Expose Content-Disposition header on NextJS - reactjs

I'm trying to get the Content-Disposition header in a response from an external API using axios.
Despite the header being present in Chrome DevTools Network Response, I can't seem to have access to that specific header from server.
I found this article talking about exposing the Content-Disposition header through Access-Control-Expose-Headers but I'm not quite sure how to implement it in Nextjs.
I tried editing the next.config.js file like below, by following directions from this Nextjs Documentation page, regarding security headers, but had no luck
/** #type {import('next').NextConfig} */
module.exports = {
reactStrictMode: true,
async headers() {
// to allow specific headers to appear in requests
// https://nextjs.org/docs/advanced-features/security-headers
const securityHeaders = [
// important
{ key: "Access-Control-Expose-Headers", value: "Content-Disposition" },
]
return [
{
source: '/:path*', // req path
headers: securityHeaders
}
]
}
}
This is the API call I made using axios:
// lib/utils.js
export async function downloadFile(collectionName: string, documentId: string) {
const res = await axios.get(
`https://api.myapiendpoint.com/file/${collectionName}/${documentId}`
);
console.log(res.headers);
}
Chrome DevTools log:
console.log output:
// these are the only headers I receive
{
"content-length": "195687",
"content-type": "text/csv; charset=utf-8",
"last-modified": "Thu, 10 Feb 2022 16:00:05 GMT"
}

I am 99.99% sure and I believe this is most probably backend issue. I had the exact same problem when i needed to implement download pdf/csv logic (with file name) which required content-disposition header to be accessed from response.
Now, no matter what I tried I always got to see that header in dev tools and also in postman but not in my console. After lots of efforts and convincing my backend team member, it turned out that backend didn't expose it.
Check your backend (whatever technology it uses), the problem lies there ;)

Related

Getting Axios from React to Work with Spring Boot + Spring Boot Security

I have created a react app with a spring boot backend but I'm having trouble pulling the data through with axios.
I have checked numerous SO posts as well as documentation with spring to no avail. I was initially blocked by CORS but I was able to resolve that with Spring Security. However, spring security requires authentication, I've been using the default user "user" with the randomly generated password (since I can't get a newly defined user/password defined with AuthenticationManagerBuilder to work just with queries against the server directly in a browser but that's an issue for another day). Below is my configuration file for the server.
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
http.cors().and();
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
configuration.setAllowedMethods(Arrays.asList("GET", "PUT", "POST", "DELETE", "OPTIONS", "HEAD"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
My server runs on localhost port 9898, the query I'm initially trying to pull data from on the front end is a get by id for contact info which goes against http://localhost:9898/api/Contact/1
when I successfully call the server from a browser directly the header details are as depicted:
call from browser to server general and response headers
call from browser to server request headers
notice that authorization header is actually there in the request.
For the RestController I've got cross origin set to the client running on port 3000. I do have a header being adding in the getContactMethod as suggested in a tutorial but I don't think this actually changed anything since I have this header being set in the configuration file anyway.
#CrossOrigin(origins = "http:localhost:3000")
#RestController
#RequestMapping("/api/Contact")
public class ContactController {
#Autowired
private ContactRepository ContactRepository;
#GetMapping("/")
public List<Contact> getContacts(){
return this.ContactRepository.findAll();
}
#GetMapping("/{id}")
public Contact GetContact(#PathVariable Long id, HttpServletResponse response){
response.setHeader("Access-Control-Allow-Origin", "**");
return ContactRepository.findById(id).orElse(null);
}
For the client I have a file creating an axios instance, I'm not sure if this part is right since I never reference the specific instance again but maybe axios can figure this out on it's own if there is only one instance.
import axios from "axios";
const api = axios.create({
baseURL: "http://localhost:9898",
});
// api.defaults.headers.common = {
// 'X-Requested-With': 'XMLHttpRequest'
// };
export default axios;
Now for the actual page on the front end I am attempted to load the requested data into the state variable from the useEffects event, which will need to be modified a little bit further but I can't go forward with that until the request works.
I've got numerous headers loaded in based on a combination of what I've come across online but the one I want to focus on is the authentication since that won't actually show up in the request when I look with dev tools on the network. I've got the password which is randomly set by spring security each time the back end is run hard coded and then this hard coded user:password value is encoded and added to the headers. Below is the relevant code:
const tok = 'user:9de3a921-a4af-4d51-b8d7-cf37b208916e';
const hash = btoa(tok);
const Basic = 'Basic ' + hash;
const headers = {
"Cache-Control": "no-cache",
"Accept-Language": "en",
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "http://localhost:3000",
"Access-Control-Allow-Methods": "DELETE, POST, GET, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With",
//"Authorization": "Basic dXNlcjowM2VhN2JhYS1mMTQ0LTQ5YWMtOGFhMy02NDE4YWJiNzdhMTk=",
'Authorization': `Basic ${hash}`,
};
useEffect(() =>{
console.log(Basic);
axios.get("http://localhost:9898/api/Contact/1", headers)
.then((res) => {
console.log("data " + res.data);
console.log("response header " + res.headers['Authorization']);
setInfo(res.data);
}).catch(err => console.log("error found " + err));
console.log(info);
}, []||[]);
When this is run I get a 401 unauthorized but for some reason the authorization header doesn't show up in the request headers.
General and response headers for request from client to server
Request headers for request from client to server
I feel like I'm fairly close with this but most of the tutorials on the spring site are simpler and the best practices for spring security have changed over the years so there is a lot of conflicting information and incomplete examples on the web. I figure I either have an issue in the security configuration or I guess I've set the headers up incorrectly but I don't have enough experience with spring and react I've just been troubleshooting in circles for a couple days.
Sources tried already (had to put some spaces in the links since I just made this account to post a question):
https://stackoverflow com/questions/36968963/how-to-configure-cors-in-a-spring-boot-spring-security-application/37610988#37610988
I should mention with this one below I added in .antMatchers(HttpMethod.Options, "/**").permitAll() and the headers were different but the request still didn't work and eventually the server would just crash shortly after starting with it
https://stackoverflow com/questions/41075850/how-to-configure-cors-and-basic-authorization-in-spring-boot/60933851#60933851
https://stackoverflow com/questions/58901586/how-to-fix-spring-security-authorization-header-not-being-passed
https://spring io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter
https://spring io/guides/gs/rest-service-cors/
https://spring io/guides/gs/rest-service/
https://docs.spring io/spring-security/reference/reactive/integrations/cors.html
https://www.baeldung com/spring-security-cors-preflight
There is an issue with how the headers are are being passed to axios. The axios documentation defines axios.get like this axios.get(url[, config])
There are two parameters here. The first is the url, and it is required. The second is an optional config object.
The config object has a headers field.
You should pass in the headers like this:
const headers = {
'Cache-Control': 'no-cache',
'Accept-Language': 'en',
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': 'http://localhost:3000',
'Access-Control-Allow-Methods': 'DELETE, POST, GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With',
//"Authorization": "Basic dXNlcjowM2VhN2JhYS1mMTQ0LTQ5YWMtOGFhMy02NDE4YWJiNzdhMTk=",
Authorization: `Basic ${hash}`
};
const config = {
headers
};
axios.get('http://localhost:9898/api/Contact/1', config);
I was looking back at the similar post I referenced earlier How to configure CORS in a Spring Boot + Spring Security application?
and I tried out the 4th most updated answer by user Soroosh Khodami Mar 11, 2021 and used their SecurityConfig.
#Override
protected void configure(HttpSecurity http) throws Exception {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedHeaders(List.of("Authorization", "Cache-Control", "Content-Type"));
corsConfiguration.setAllowedOrigins(List.of("http://localhost:3000"));
corsConfiguration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PUT","OPTIONS","PATCH", "DELETE"));
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setExposedHeaders(List.of("Authorization"));
// You can customize the following part based on your project, it's only a sample
http.authorizeRequests().antMatchers("/**").permitAll().anyRequest()
.authenticated().and().csrf().disable().cors().configurationSource(request -> corsConfiguration);
}
My previous config was missing:
setAllowedHeaders(Listof("Authorization",..))
setAllowCredentials(true)
setExposedHeaders(Authorization)
this then lead me to a secondary issed which was caused by setting the header in my restcontroller get by id method.
response.setHeader("Access-Control-Allow-Origin", "**");
I got an error saying this was not allowed so then I changed it to "/**" and it still complained so I just commented it out and it worked. So if a tutorial suggests this (for me it was a youtube video about getting cors to work) I believe it is out of date/not best practice when you should set Access-Control-Allow-Origin in your Web Security Configuration.

Blocked by CORS policy "...does not have HTTP ok status" (Amplify and ReactJS, AWS Gateway and Lambda)

I'm almost embarassed to be asking this question due to CORS support out there on SO but I can't get by:
Access to XMLHttpRequest at 'https://a93xxxxx.execute-api.eu-west-1.amazonaws.com/dev[object%20Object]' from origin 'https://www.example.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
I've even published my React project with Amplify and attempted it from the real domain name to even eliminate anything to do with the development environment (Cloud 9 running npm version 6.14.8)
I've also made a test running Chrome with the --disable-web-security flag.
My Lambda function contains the following (out of the box stub)
exports.handler = async (event) => {
// TODO implement
const response = {
statusCode: 200,
// Uncomment below to enable CORS requests
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers" : "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With",
"Access-Control-Allow-Methods" : "OPTIONS,POST,GET,PUT"
}
,
body: JSON.stringify("Hello from Lambda!")
};
return response;
};
Note that I've uncommented the CORS request part and the response statusCode is set to 200.
The code in my application that execute when a submission form is sent from the client:
uploadcontactusdata = async data => {
try {
console.log("Contact Us pressed")
const settings = {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
}
const fetchResponse = await API.post('econtactus', settings);
Notification({
title: 'Success',
message: 'Notification has been sent',
type: 'success'
});
}
catch (err) {
console.log("unable to send");
console.error(err)
}
}
I created the API Gateway + Lambda using Amplify (version 4.41.2). Not sure where else to look now. Any clues will be appreciated. Thanks
You can completely get past the need for api gateway by using appsync.
amplify add api
Choose graphql (I have not tried using rest but you shouldn't need it) choose the basic schema, edit it if you'd like, and publish. Once it's published you can create your own method. You can view this inside the AppSync UI under Schema.
type Mutation {
yourMethod(input: Input!): TableName <-- add your method to the list
}
Now inside Appsync choose Data Sources and add datasource. Give it a name, choose lambda as the type, then find your lambda in the list. Once it's added go back to your schema and find the method you created above. On the right side bar locate your method and click the attach link. Find the data source you just added. Fill out the region and lambda ARN. MAKE SURE you choose new role and not an existing one.
You might need to configure the request and response templates.
For request:
{
"version" : "2017-02-28",
"operation": "Invoke",
"payload": $util.toJson($context.args)
}
For response:
$util.toJson($context.result)
Now you can call your lambda directly from the UI and return your result without worrying about CORS or managing API Gateway.

React/Axios API Get Request Issues (CORS & Internal Server Error 500)

I am attempting to complete an axios GET request to an API and I'm running into an Internal Server Error - 500 and I'm curious if this is simply my code and/or my attempt at making this call or something else. Even though the CORS issue seems to be behind me, I'll start from the beginning just in case its related to my current issue.
My initial attempt at the request gave me the following CORS error:
...from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight
request doesn't pass access control check: It does not have HTTP ok status.
After doing a lot of research on this, I found that I could append https://cors-anywhere.herokuapp.com to my target API URL and get around this issue. So far, so good but now I am getting the following locally in my browser: net::ERR_NAME_NOT_RESOLVED
So I decided to jump over to Postman and input the given headers to access this API to see if I could find more information and I'm getting the following on Postman:
{
"timestamp": "2020-11-13T01:04:47.288+0000",
"message": "General error occurred please contact support for more details",
"error": "Internal Server Error",
"status": 500
}
Now, within the documentation of this API, it states that a 500 is a server error on their part but I'm not confident in that as I think it may just be my own doing here. So I basically have two questions...
Should the developer of the API do/change anything to avoid the CORS issue or is that a common thing to run into?
Is the 500 error response on me or them?
Below is my axios request in my App.js file of my React application. Please let me know if any other code or info is needed. Thanks so much in advance for any help!
import React, { Component } from 'react';
import './App.css';
import axios from 'axios';
class App extends Component {
state = {
events: []
}
constructor() {
super();
const proxyURL = 'https://cors-anywhere.herokuapp.com'
const URL = 'https://api.example.com/api/'
const proxiedURL = proxyURL + URL
axios.get(proxiedURL, {
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer ' + process.env.REACT_APP_AUTH_API_KEY
}
})
.then((res) => {
console.log(res.data)
})
.catch((error) => {
console.log(error)
})
}
render() {
return (
<div className="App">
<header className="App-header">
<h1>Data</h1>
</header>
</div>
);
}
}
export default App;
According to the documentation for cors-anywhere:
This API enables cross-origin requests to anywhere.
Usage:
/ Shows help /iscorsneeded This is the only resource
on this host which is served without CORS headers. /
Create a request to , and includes CORS headers in the response.
Your code is missing a trailing slash after https://cors-anywhere.herokuapp.com to work, i.e.: https://cors-anywhere.herokuapp.com/.
To answer your two other questions:
CORS issues are very common and it is up to the developer of the API to set from which domain the API can be called. More on MSDN
The 500 response means this in an internal server error, so on the server-side. Though it can be because of many reasons, like querying the wrong URL, passing unexpected data... Ideally all these should be covered and different errors would be returned every time but this is rarely the case. :)

JavaScript CORS error when uploading files with Axios

I am developing a web application with Flask on the backend and React and Redux on the frontend.
I want to add a "Change Profile Picture" option to the profile page but whenever I make a post request with axios to my /api/user/upload_image/ route, i get the following errors:
Access to XMLHttpRequest at 'http://localhost:5000/api/user/update_image' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
PATCH http://localhost:5000/api/user/update_image net::ERR_FAILED
Which is weird becuase I have set up my CORS wrapper in my Flask app like so:
self.cors = CORS(self.app, resources={r"/api/*": {"origins": "*"}})
which should allow requests to /api/ from all origins.
I also tried to do the same thing with Postman and it worked like a charm - uploaded the file and saved it to /server/public/profile_pictures/
When i try to upload regular JSON text from my react application it works as well. It bugs out on file uploads only.
Here is the JSX for the input + the event handler
<label>
Change Profile Picture
<input onChange={(e) => {
this.setState({image: e.target.files[0]})}
} type="file" name="image" />
</label>
Then i have a submit button which dispatches the following action with this.state.image as a parameter:
export const updateImage = (file) => {
return async (dispatch, getState) => {
const formData = {
user_id: getState().currentUser.user.user_id,
auth_key: getState().currentUser.auth_key,
image: file
}
Axios.patch("http://localhost:5000/api/user/update_image", formData, {
headers: {
'Content-Type' : 'multipart/form-data'
}
})
.then(response => {
dispatch({type: UPDATE_IMAGE, payload: response.data})
})
}
I tried using the built in formData method to create the JS object too but that was no good either.
Finally here is the python method which is called when the /api/user/update_image route is hit:
def update_image(self, request):
image = request.files['image']
data = request.params
image.save("./public/profile_pictures/user_p_picture_id_"+data['user_id']+".jpg")
fsql.update("""UPDATE users SET profile_picture = %s WHERE user_id = %s""", ("/public/profile_pictures/user_p_picture_id_"+data['user_id']+".jpg", data['user_id']))
return jsonify({
"error_code" : "200",
"error_message" : "Success"
})
I actually solved this about a week and a half ago but I checked the status today.
So the solution was to make a few changes to my config parameter and CORS parameters. Here is the configs i am using right now:
config = {
'ORIGINS': [
'http://localhost:3000', # React
'http://127.0.0.1:3000', # React
],
'SECRET_KEY': '...' #secret key
self.cors = CORS(self.app, resources={
r'/api/*': {
"Access-Control-Allow-Origin": config["ORIGINS"],
"Access-Control-Allow-Credentials": True,
'supports_credentials': True
},
},
supports_credentials = True,
expose_headers = "*"
)
self.app.config['UPLOAD_FOLDER'] = r'/*' # Change this to only the folder you want to save images to
self.app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # Change this according to your file size
This solved my CORS and file transport issues.
I really hope this helps someone. The CORS docs on flask-cors do not cover everything in regards to file uploading and session storage so we kind of have to solve the errors without knowing how everything works - like trying to solve a puzzle with missing pieces.
HMU in messages if you have any good tools for CORS in flask which are well documented and have a community around them.

Microsoft Azure - OAuth2 - "invalid_request"

I would like to connect my app with Microsoft Graph. I created my web-app in Azure (I have my client_id and client_secret). I am able to send a request to get the authorization code from https://login.microsoftonline.com/common/oauth2/v2.0/authorize.
The problem is that when I send a POST request in order to get the acess token from https://login.microsoftonline.com/common/oauth2/v2.0/token (exactly like said here in the "Using permissions" section) using Postman (with form-data option), I get an "AADSTS9000410: Malformed JSON" error:
{
"error": "invalid_request",
"error_description": "AADSTS9000410: Malformed JSON.\r\nTrace ID: f5c1dd4b-ad43-4265-91cb-1b7392360301\r\nCorrelation ID: 1dea54ed-bb43-4951-bc9e-001877fe427b\r\nTimestamp: 2019-01-14 21:38:42Z",
"error_codes": [9000410],
"timestamp": "2019-01-14 21:38:42Z",
"trace_id": "f5c1dd4b-ad43-4265-91cb-1b7392360401",
"correlation_id": "1dea54ed-bb43-4951-bc9e-001878fe427b"
}
Moreover, when I send the same request with a raw option in Postman, I get "AADSTS900144: The request body must contain the following parameter: 'grant_type'":
{
"error": "invalid_request",
"error_description": "AADSTS900144: The request body must contain the following parameter: 'grant_type'.\r\nTrace ID:a7c2f8f4-1510-42e6-b15e-b0df0865ff00\r\nCorrelation ID:e863cfa9-0bce-473c-bdf6-e48cfe2356e4\r\nTimestamp: 2019-01-1421:51:29Z",
"error_codes": [900144],
"timestamp": "2019-01-14 21:51:29Z",
"trace_id": "a7c2f8f4-1510-42e6-b15e-b0df0865ff10",
"correlation_id": "e863cfa9-0bce-473c-bdf6-e48cfe2356e3"
}
However, when I remove application/json in my header in Postman, and I put x-www-form-urlencoded option, everything looks fine.
I can only send POST requests with a JSON format in my application.
Does Microsoft Graph support JSON format for POST requests?
Is it a Postman issue?
I ran into a similar issue, but realized that there was a mismatch between the Content-Type: application/x-www-form-urlencoded header and the JSON-formatted request body. If you refer to this documentation, you'll see that the request body needs to be URL encoded (concatenated with ampersands, encoded entities, etc.), which ultimately resolved my issue. So, I don't believe this is an issue with Postman or MS APIs but, rather, just incorrect formatting of your request body.
I'm not sure what language your app uses, but here's an example using Node and Express that works for me:
const fetch = require('node-fetch')
const { URLSearchParams } = require('url')
async function getAccessToken(req, res, next) {
try {
const response = await fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
// Previously I was doing `body: JSON.stringify({...})`, but
// JSON !== URL encoded. Using `URLSearchParams` (or whatever
// the equivalent is in your language) is the key to success.
body: new URLSearchParams({
client_id: YOUR_CLIENT_ID_HERE,
scope: 'User.Read Calendars.Read',
redirect_uri: YOUR_REDIRECT_URL_HERE,
grant_type: 'authorization_code',
client_secret: YOUR_CLIENT_SECRET_HERE,
code: req.query.code
}
})
const json = await response.json()
// `json` will have `access_token` and other properties
} catch (err) {
throw err
}
}
Hope that helps!
It is neither a Microsoft nor a Postman issue, it is simply how OAuth defines the token workflow. This is defined in RFC 6749 - Section 4.1.3:
The client makes a request to the token endpoint by sending the following parameters using the application/x-www-form-urlencoded format per Appendix B with a character encoding of UTF-8 in the HTTP request entity-body

Resources