Is a fetch request token in a next.js app visible to a client side user?
I must to prepare an application using GitHub GraphQL API. I though about using fetch request with a bearer token for it, but I have a doubts about security and good practices in my app.
fetch((`https://api.github.com/graphql`, {
method: 'POST',
headers: {
"Content-Type": 'application/json',
'Authorization': 'Bearer ' + TOKEN,
},
body: JSON.stringify({
query: `
{
search(query: "is:public", type: REPOSITORY, first: 10) {
repositoryCount
pageInfo {
endCursor
startCursor
}
edges {
node {
... on Repository {
name
}
}
}
}
}
`
})
}))
According to the Next.js docs, you need to create a .env.local file at the root of the project directory and add the secrets.
When adding secrets, you need to prefix the secret with NEXT_PUBLIC_ to expose it to the browser. For example:
NEXT_PUBLIC_GITHUB_TOKEN=amogussus
According to the docs:
The value will be inlined into JavaScript sent to the browser because of the NEXT_PUBLIC_ prefix.
Then you might add it into your code with process.env.NEXT_PUBLIC_GITHUB_TOKEN
Related
I'm trying to make a request in a local file, but I don't know when I try to do on my computer show me an error. Is possible make a fetch to a file inside your project?
// Option 1
componentDidMount() {
fetch('./movies.json')
.then(res => res.json())
.then((data) => {
console.log(data)
});
}
error: Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0 at App.js: 10 --> .then(res => res.json())
// Option 2
componentDidMount() {
fetch('./movies.json', {
headers : {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
})
.then( res => res.json())
.then((data) => {
console.log(data);
});
}
error1: GET http://localhost:3000/movies.json 404 (Not Found) at App.js:15 --> fetch('./movies.json', {
error2: Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0 at App.js: 10 --> .then(res => res.json())
// This works
componentDidMount() {
fetch('https://facebook.github.io/react-native/movies.json')
.then( res => res.json() )
.then( (data) => {
console.log(data)
})
}
Try to place your json file in the public folder like so :
public/movies.json
and then fetch using
fetch('./movies.json')
or
fetch('movies.json')
I have experienced the same problem previously. When I place the json file in the public folder, problem is solved.
When using fetch, React normally reads asset/resources files in the public folder.
You are trying to serve a static file with a fetch command, which inherently requires the file to be served by a server. To resolve the issue, you have a few options available to you. I am going to outline the two that are most commonly suggested for such a thing:
Use Node.js and something like expressjs to host your own server that serves the file you want to fetch. While this procedure might require more effort and time, it is certainly more customizable and a good way to learn and understand how fetching from a backend works.
Use something like Chrome Web Server to easily set up a very simple server to serve your file on your local network. Using this method, you have very little control over what you can do with said web server, but you can quickly and easily prototype your web application. However, I doubt there's a way to move this method to production.
Finally, there are other options where you can upload one or more files online and fetch them from an external URL, however this might not be the optimal strategy.
Your JSON file needs to be served by the server so you need the express server (or any other). In this example we are using express.
Note: you can also download git repo
App.js File
import React, { Component } from 'react';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null
};
}
componentDidMount() {
const myHeaders = new Headers({
"Content-Type": "application/json",
Accept: "application/json"
});
fetch("http://localhost:5000/movie", {
headers: myHeaders,
})
.then(response => {
console.log(response);
return response.json();
})
.then(data => {
console.log(data);
this.setState({ data });
});
}
render() {
return <div className="App">{JSON.stringify(this.state.data)}</div>;
}
}
export default App;
server.js
var express = require("express");
var data = require('./movie.json'); // your json file path
var app = express();
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
app.get("/movie", function(req, res, next) {
res.send(data);
});
app.listen(5000, () => console.log('Example app listening on port 5000!'))
I was encountering the same error and there are two changes I made in my code to get rid of the error. Firstly, you don't need an express server to serve your files you can read data from a local json file inside your public folder in your create-react-app directory.
const getData=()=>{
fetch('data.json',{
headers : {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}
)
.then(function(response){
console.log(response)
return response.json();
})
.then(function(myJson) {
console.log(myJson);
});
}
useEffect(()=>{
getData()
},[])
First, as suggested in some of the answers above ensure that your json file is inside the public folder and the path parameter inside the fetch function is correct as above. Relative paths didn't work for me.
Second, set the headers as shown. Removing the headers part from my fetch call was still giving me this error.
a simple solution to this is to use live server extension (if you use vs code)
Say that i have the following file test.html
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<script>
var depdata;
depdata = fetch("test1.geojson")
.then((data) => {
return data;
});
depdata.then(function(data) {console.log(data)})
</script>
</body>
</html>
When access the file in the firefox through file://... I get the following error:
Cross-Origin Request Blocked:....
When I followed the error on firefox I got the following explanation
CORS requests may only use the HTTPS URL scheme, but the URL specified by the request is of a different type. This often occurs if the URL specifies a local file, using a file:/// URL.
To fix this problem, simply make sure you use HTTPS URLs when issuing requests involving CORS, such as XMLHttpRequest, Fetch APIs, Web Fonts (#font-face), and WebGL textures, and XSL stylesheets.
So the as far as I understand we just need to access the test.html through HTTP. The most straight forward way around this problem was the python simple http server. In the terminal.
> cd directory of the project.
> python3 -m http.server 8000 --bind 127.0.0.1
Then in the browser:
http://localhost:8000/test.html
My go-to approach is to use express-generator to set up a quick local server, then run ngrok (free tier is fine) and point your app to the url it creates. This has the advantage of letting you easily test your fetching in the iOS simulator or Android emulator, as well as on a device not tethered to your computer. Plus, you can also send the url to people testing your app. Of course, there would need to be a way for them to manually input that url so the app could set it as the fetch endpoint.
I got it working rather very simple way - no express / webserver really needed. Just do :
import data from '../assets/data.json';
and use the json data like this (say if it is a JsonArray) :
data.map(movie ...
Do this in App.js or some other class extending React.Component,
The error
Unexpected token < in JSON at position 0
comes from the HTML file that is returned if the request is unsuccessful. The first element (at position 0) of an HTML file is typically a '<'. Instead of a JSON, an attempt is made to read in an HTML file.
You can find the returned HTML File in the Inspect Tool -> Network -> Erroneous file marked in red -> Reponse. There you can see what the specific error is. Example Error Message
To fix the error for me, it helped to move the file to be imported to the Public folder of my React project and then import it like this from a file in the 'src' folder: fetch('dataTemplate.json')
You can place your json file in the public folder. In your React component you can use userEffect (). You don't need Express.js for this case.
React.useEffect(() => {
fetch("./views/util/cities.json")
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(myJson);
});
});
To fetch local files, a good alternative:
npm install file-fetch
to read a file:
const fileFetch = require('file-fetch')
fileFetch('./public/user.json').then((res) => {
res.body.pipe(process.stdout)
})
See doc
I am trying to configure a proxy for my API requests using http-proxy-middleware, which the create react app docs suggest. I set up my proxy like this, in the setupProxy.js file:
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
app.use(
createProxyMiddleware("/post", {
target: 'https://postman-echo.com',
changeOrigin: true,
logLevel: 'debug'
})
);
};
then, I do a simple POST to an endpoint:
const response = await fetch("/post", {
method: 'POST',
headers: {
'content-type': 'application/json;charset=UTF-8'
},
body: JSON.stringify({ foo1: "bar1", foo2: "bar2" })
});
console.log(await response.json());
According to the http-proxy-middleware docs, I should expect a proxy that does something like this:
[HPM] POST /post -> https://postman-echo.com/post
But instead, the debugger shows this:
[HPM] POST /post -> https://postman-echo.com
The path, /post, does not get appended to the proxy request. The target should actually be https://postman-echo.com/post. My client gets a 404 error because https://postman-echo.com on its own does not match anything on the backend.
If it did reroute correctly, I should expect the same results as a CURL request
curl -X POST -F 'foo1=bar1' -F 'foo2=bar2' https://postman-echo.com/post
{"args":{},"data":{},"files":{},"form":{"foo1":"bar1","foo2":"bar2"},"headers":{"x-forwarded-proto":"https","x-forwarded-port":"443","host":"postman-echo.com","x-amzn-trace-id":"Root=1-61200c54-7b5809be3e78040f09edcd42","content-length":"240","user-agent":"curl/7.64.1","accept":"*/*","content-type":"multipart/form-data; boundary=------------------------bb54b419e41f4a4a"},"json":null,"url":"https://postman-echo.com/post"}%
But I 404 because the path is not added. Why is the path being left out?
I created a simple app that recreates my issue. This looks similar to this issue but they are not the same (I am using the same syntax as the answer suggests).
I got it working. The problem was that I was testing with an endpoint that 404'd. I got confused because the debugger doesn't append /post to the end of the log like the docs say it should.
So I have 2 applications:
an Adonis API server accessible via http://10.10.120.4:3333
A SSR app using Nuxt.js accessible via http://10.10.120.4:80
The Nuxt.js app is accessible outside using url http://my-website.com. I have axios module with this config
axios: {
baseURL: '0.0.0.0:3333/api',
timeout: 5000
}
Now the problem is, when I am requesting data with asyncData it works, but when the request was made from outside asyncData, say created() for example, it throws an error saying the url http:0.0.0.0:3333 is missing which is true since it's already running in the browser and not in the server.
The first solution that I've tried is to change the baseURL of the axios module to this
axios: {
baseURL: 'http://my-website.com/api',
timeout: 5000
}
But it seems nuxt server can't find it, so I think the solution is to make proxy and installed #nuxtjs/proxy.
And this is my proxy config in nuxt.config.js
{
proxy: {
'/api': 'http://my-website.com:3333',
}
}
and then I just changed my axios baseURL to
http://my-website.com/api
But again it didn't work.
My question is, how do you deal with this kind of scenario? Accessing different server from browser?
When using Proxy in a nuxt project you need to remove baseUrl and set proxy to true as seen below.
axios: {
// Do away with the baseUrl when using proxy
proxy: true
},
proxy: {
// Simple proxy
"/api/": {
target: "https://test.com/",
pathRewrite: { "^/api/": "" }
}
},
when making a call to your endpoint do:
// append /api/ to your endpoints
const data = await $axios.$get('/api/users');
checkout Shealan article
I am new to The Movie Database API. I created a Vue project and when I npm run serve and go to my localhost I see my app for 1 second and after that, I get a 403 with this error:
403 ERROR The request could not be satisfied. Request blocked. We
can't connect to the server for this app or website at this time.
There might be too much traffic or a configuration error. Try again
later, or contact the app or website owner. If you provide content to
customers through CloudFront, you can find steps to troubleshoot and
help prevent this error by reviewing the CloudFront documentation.
Generated by cloudfront (CloudFront) Request ID:
Pp4URUVnBrt7ovECZ1OEJPM9nW_N5ZE4sgxMuTAbG3A80reupMAAsw=="
When I debug I get this:
...\MovieAppVue\src\services\Network.js:1
import config from '../config/index'
^^^^^^
SyntaxError: Cannot use import statement outside a module
at Module._compile (internal/modules/cjs/loader.js:895:18)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:995:10)
at Module.load (internal/modules/cjs/loader.js:815:32)
at Function.Module._load (internal/modules/cjs/loader.js:727:14)
at Function.Module.runMain (internal/modules/cjs/loader.js:1047:10)
at internal/main/run_main_module.js:17:11
aiting for the debugger to disconnect...
When I test API key on their site I get 200 OK responses, so the API key is good. Can someone please check my repo to see what I am doing wrong? I don't know what I am missing here.
https://github.com/ivanradunkovic/MovieApp
import config from '../config/index'
import {storageService, storageKeys} from '../mixins/storageService'
class Network {
static methods = {
GET: 'GET',
POST: 'POST'
}
addApiKey(url) {
return `${url}?api_key=${config.API_KEY}`
}
addSessionId(url) {
const sessionId = storageService.methods.storageGet(storageKeys.SESSION_ID)
return sessionId ? `${url}&session_id=${sessionId}` : url
}
constructUrl(url, queryParams) {
url = this.addApiKey(`${config.API_BASE_URL}${url}`)
url = this.addSessionId(url)
for (let key in queryParams) url += `&${key}=${queryParams[key]}`
return url
}
async fetch(url, method, options = {}) {
const res = await fetch(this.constructUrl(url, options.params), {
method,
body: JSON.stringify(options.data),
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
}
})
return await res.json()
}
}
export default Network
We had the same problem (same exact error message , anyway) in AWS Cloudfront when we ran parallel tests (hundreds on them all at once). We did not have the problem we ran them one after another.
In our case , we hit the "Request time out per origin". Our S3 origin got too slow under load.
Here are the list of limits:
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html#limits-web-distributions
One way to confirm if indeed this is what root cause is , is to go to AWS Console and:
Cloudfront --> Cloudfront Distributions --> Choose the CF distribution in question ---> Click on "Usage" on the left frame.
That will show you graph of how much heavy lifting Cloudfront did and when. If the time matches, then the root cause is the same.
I have a web app that is provided by an express.js server (web app server). The web app server handles the user login via passport.js and the passport-azure-ad npm package. I am using the OIDCStrategy for this.
I am also hosting a REST api via another server (rest backend). I want to secure this backend via passport.js and the passport-azure-ad npm package using the BearerStrategy. For that I want to define a scope in the web app server passport configuration so that the access_token of the web app server can be passed via a cookie to my web app and from there be used to access the rest backend.
My issue: with my current configuration, I receive 401 access denied while trying to access my backend api with the access_token. access_token invalid is the error message: {"name":"AzureAD: Bearer Strategy", "msg":"authentication failed due to: error: invalid_token","v":0}
I think I should be redirected to a permission page while signing in but it does not. So I guess my access token is actually not valid.
Web app server passport configuration:
passport.use(
new OIDCStrategy(
{
clientID: credsAzureAD.clientID,
identityMetadata: credsAzureAD.identityMetadata, // here is use the web app tenant id
clientSecret: credsAzureAD.clientSecret,
callbackURL: credsAzureAD.returnURL,
redirectUrl: credsAzureAD.returnURL,
skipUserProfile: true,
responseType: 'code',
responseMode: 'query',
scope: 'api://test/Write.Contributor',
useCookieInsteadOfSession: false,
passReqToCallback: false
},
(issuer, sub, profile, accessToken, refreshToken, done) => {
user.accessToken = accessToken;
return done(null, user);
}
)
);
I try to use the scope where api://test is my REST API application ID uri and /Write.Contributor is the scope that I defined in azure active directory.
My REST backend server passport configuration:
const options = {
identityMetadata: azureAD.identityMetadata, // here I use the backend server tenant id
clientID: azureAD.clientID,
issuer: azureAD.issuer, // here I use the backend server tenant id
passReqToCallback: false,
};
const bearerStrategy = new BearerStrategy(options, function(token, done) {
done(null, {}, token);
});
I have created my backend server in azure active directory via application registration and created the named scope and application id above. I also have whitelisted my web app clientId there as a authorized client applications.
I try to call following route and receive 401:
app.get(
'/testtest',
cors(),
passport.authenticate('oauth-bearer', { session: false }),
function(req, res) {
var claims = req.authInfo;
console.log('User info: ', req.user);
console.log('Validated claims: ', claims);
res.status(200).json({ name: claims['name'] });
}
);
This is my rest call from my vue web app:
let headers = {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.user.accessToken}`,
'cache-control': 'no-cache'
};
const apiClient = axios.create({
baseURL,
headers
});
apiClient.get('/testtest').then( resp => console.log( resp));
I am taken the access token as is no decoding/encoding.
Any support would be very much appreciated. Thank you!
As #Jim Xu suggested in the comments, adding the application scope of the REST api to the client application via Azure Portal helped solving the issue. But I was also using the wrong token.
instead of using the accessToken from the verify function parameter list, I now use the param from the verify function parameter list.
(iss, sub, profile, access_token, refresh_token, params, done) => {
// access_token did not work
// id_token can be used as an accessToken
user.accessToken = params.id_token;
...
}