I've copied exactly what's written in the sample code here: https://github.com/firebase/functions-samples/blob/master/authorized-https-endpoint/functions/index.js
but I keep getting this error when trying to make a normal get request to the /savedProfiles endpoint
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://us-central1-my-app.cloudfunctions.net/savedProfiles. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
This is my code:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const express = require('express');
const cookieParser = require('cookie-parser')();
const cors = require('cors')({origin: true});
const app = express();
// Express middleware that validates Firebase ID Tokens passed in the Authorization HTTP header.
// The Firebase ID token needs to be passed as a Bearer token in the Authorization HTTP header like this:
// `Authorization: Bearer <Firebase ID Token>`.
// when decoded successfully, the ID Token content will be added as `req.user`.
const validateFirebaseIdToken = (req, res, next) => {
console.log('Check if request is authorized with Firebase ID token');
if ((!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) &&
!req.cookies.__session) {
console.error('No Firebase ID token was passed as a Bearer token in the Authorization header.',
'Make sure you authorize your request by providing the following HTTP header:',
'Authorization: Bearer <Firebase ID Token>',
'or by passing a "__session" cookie.');
res.status(403).send('Unauthorized');
return;
}
let idToken;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
console.log('Found "Authorization" header');
// Read the ID Token from the Authorization header.
idToken = req.headers.authorization.split('Bearer ')[1];
} else {
console.log('Found "__session" cookie');
// Read the ID Token from cookie.
idToken = req.cookies.__session;
}
admin.auth().verifyIdToken(idToken).then((decodedIdToken) => {
console.log('ID Token correctly decoded', decodedIdToken);
req.user = decodedIdToken;
return next();
}).catch((error) => {
console.error('Error while verifying Firebase ID token:', error);
res.status(403).send('Unauthorized');
});
};
app.use(cors);
app.use(cookieParser);
app.use(validateFirebaseIdToken);
app.get('/savedProfiles', (req, res) => {
res.send(`Hello ${req.user.name}`);
});
// This HTTPS endpoint can only be accessed by your Firebase Users.
// Requests need to be authorized by providing an `Authorization` HTTP header
// with value `Bearer <Firebase ID Token>`.
exports.savedProfiles = functions.https.onRequest(app);
Shouldn't app.use(cors); prevent these sorts of errors?
So I managed to get this to work without even using express. Here's what I came up with:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const cookieParser = require('cookie-parser')();
const cors = require('cors')({
origin: 'http://localhost:8100'
});
exports.savedProfiles = functions.https.onRequest((req, res) => {
cors(req, res, () => {
console.log('Check if request is authorized with Firebase ID token');
if ((!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) &&
!req.cookies.__session) {
console.error('No Firebase ID token was passed as a Bearer token in the Authorization header.');
res.status(403).send('Unauthorized');
return;
}
let idToken;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
// Read the ID Token from the Authorization header.
idToken = req.headers.authorization.split('Bearer ')[1];
} else {
// Read the ID Token from cookie.
idToken = req.cookies.__session;
}
admin.auth().verifyIdToken(idToken).then((decodedIdToken) => {
req.user = decodedIdToken;
res.status(200).send("SUCCESS");
return;
}).catch((error) => {
console.error('Error while verifying Firebase ID token:', error);
res.status(403).send('Unauthorized');
});
});
});
I did some test and on my PC the above cors setup works as expected.
Check the http response status: if I reproduce an error status (for example a 403) in my setup the Access-Control-Allow-Origin header is not present.
In validateFirebaseIdToken try to add:
res.set('Access-Control-Allow-Origin', '*');
just before res.status(403).send('Unauthorized'); expression to enable CORS also when errors happens.
Related
I have a small API made in nodejs with express. A while ago I did not touch it and everything worked perfectly. Only now have I decided to implement JsonWebToken. In Postman, the login works fine, however, when trying to send the token as a header in a request I get an error. When i don't send the token in the request, response successfull (obviously since there is no token, the endpoint returns a 401 to me).
If I try to do it after authenticating (saving the token in an environment variable) and this time assigning it to the header, the following happens
If I send anything if it works, apparently it has to do with the length of the token.
I have tried it outside of postman, and the same thing happens, so the error does not seem to be from postman.
I don't know how to solve the problem, apparently nodejs does not handle the request by the length of the token.Is there a way to expand that?
The nodejs server entry point is:
// Enviroment process
require("dotenv").config();
// Body Parser
const bodyParser = require("body-parser");
const cors = require("cors");
// Express server
const app = require("express")();
app.use(cors());
// BodyParser middleware
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// Routes middleware
app.use(require("./routes/index"));
// Run server
app.listen(process.env.PORT, () => {
console.log(`Escuchando en el puerto ${process.env.PORT}`);
});
Routes:
const express = require("express");
const { checkToken } = require("../middlewares/authentication");
const app = express();
/// Base Routes
app.get(
"/equipments",
[checkToken],
require("../controllers/equipment/get_all.controller")
);
module.exports = app;
The checkToken middleware:
const jwt = require("jsonwebtoken");
const checkToken = (req, res, next) => {
const token = req.get("token") || req.query.token;
jwt.verify(token, process.env.SEED, (err, decoded) => {
if (err) {
return res.status(401).json({
ok: false,
error: {
message: "Invalid Token",
},
});
}
req.user = decoded.user;
next();
});
};
module.exports = {
checkToken,
};
The .env variables:
// Node env (development | production)
NODE_ENV=development
// Server Port
PORT=3000
// Token Time Expiration
TOKEN_EXPIRES=48h
// Token Seed
SEED=exampleseed
UPDATE
When I send the token through the body of the request, the error does not occur, and everything works correctly (obviously changing the middleware so that it receives it by the body). The problem is when I send it by headers or a query parameter.
const checkToken = (req, res, next) => {
// const token = req.get("token") || req.query.token;
const token = req.body.token;
jwt.verify(token, process.env.SEED, (err, decoded) => {
if (err) {
return res.status(401).json({
ok: false,
error: {
message: "Invalid Token",
},
});
}
req.user = decoded.user;
next();
});
};
UPDATE AND SOLUTION:
After trying only those files, I realized that the error did not come from these. The problem was in the authentication. When creating the token I used the information of the logged in user, however, I had not realized that it had a base64 image field.
// ... after login, user object contains object information
let token = jwt.sign(
{
user: {
id: user.id,
name: user.name,
image: user.image.base64Url
},
},
process.env.SEED,
{ expiresIn: process.env.TOKEN_EXPIRES }
);
The length of the base64 image made the token extremely long. Then when making the request and sending a string token with many characters, the reading error occurrs (Error: read ECONNRESET).
The solution was to ignore the image field when creating the token.
Finally, before an error of the same type, check that a field that contains too much information is not being sent.
I've handled my server side's basic auth with nodejs, Please refer the code below.
module.exports = basicAuth;
// require("dotenv").config({ path: "./.env" });
require("dotenv/config");
// async function basicAuth(req, res, next) {
function basicAuth(req, res, next) {
// check for basic auth header
if (
!req.headers.authorization ||
req.headers.authorization.indexOf("Basic ") === -1
) {
return res.status(401).json({ message: "Missing Authorization Header" });
}
console.log(req.headers.authorization);
// verify auth credentials
const base64Credentials = req.headers.authorization.split(" ")[1];
// console.log(base64Credentials);
const credentials = Buffer.from(base64Credentials, "base64").toString(
"ascii"
);
const [username, password] = credentials.split(":");
// const user = await userService.authenticate({ username, password });
let user = 0;
if (
username == process.env.API_USERNAME &&
password == process.env.API_PASSWORD
) {
user = 1;
}
if (!user) {
return res
.status(401)
.json({ message: "Invalid Authentication Credentials" });
}
next();
}
I've added app.use(cors()) in my app.js and I'm able to access all routes using basic authentication.
I've written my front end application using react and I'm using axios to fetch the data using the routes that I created. Please note the same API's work when I try to access it without using basic auth.
Below is the code for accessing data using axios.
try {
require("dotenv").config();
console.log(this.state.params);
let urlparam = "http://localhost:5000/users/" + this.state.params;
let result;
result = await axios({
url: "http://localhost:5000/users",
method: "get",
withCredentials: true,
headers: {
authorization: "Basic c2Fsb29uOnNhbG9vbg==",
},
});
} catch (err) {
console.log(err);
}
Using the above code I get:
The requested resource requires user authentication.
on Edge browser and on Google chrome I get the error:
Access to XMLHttpRequest at 'http://localhost:5000/users' 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.
and
xhr.js:178 GET http://localhost:5000/users net::ERR_FAILED
Please bear in mind I've added and used the cors middleware for all routes and it was working previously.
I even tried passing auth parameters separately like
auth:{username:"",password:""}
it still wont work
I had to include
app.use(basicAuth) below app.use(cors()) in the app.js file.
I am trying to send a new membership from a form in react to my express server to add to the mailchimp memberlist I am getting a cors error and I don't know if I am missing any proxys. I want a user to be able to sign up in react and then it sends it to the mailchimp database
I have been able to get the members list but I am not allowed to post to it :
This is my express backend :
const express = require('express');
const Mailchimp = require('mailchimp-api-v3');
require('dotenv').config();
var request = require('superagent');
var mc_api_key = process.env.REACT_APP_MAILCHIMP_API;
var list_id = process.env.REACT_APP_LIST_ID;
const app = express();
const mailchimp = new Mailchimp(mc_api_key);
const port = process.env.PORT || 5000;
// console.log that your server is up and running
app.listen(port, () => console.log(`Listening on port ${port}`));
app.use((request, response, next) => {
response.header("Access-Control-Allow-Origin", "*");
response.header("Access-Control-Allow-Headers", "Content-Type");
next();
});
// Routes
app.post('/signup', function (req, res) {
request
.post('https://' + 'us20' + '.api.mailchimp.com/3.0/lists/' + list_id + '/members/')
.set('Content-Type', 'application/json;charset=utf-8')
.set('Authorization', 'Basic ' + new Buffer('any:' + mc_api_key ).toString('base64'))
.send({
'email_address': req.body.email,
'status': 'subscribed',
'merge_fields': {
'FNAME': req.body.firstName,
'LNAME': req.body.lastName
}
})
.end(function(err, response) {
if (response.status < 300 || (response.status === 400 && response.body.title === "Member Exists")) {
res.send('Signed Up!');
} else {
res.send('Sign Up Failed :(');
}
});
});
This is where I am trying to fetch in react in my app.js file :
onSubmit = (e,email) => {
e.preventDefault()
this.setState({user:email})
fetch('http://localhost:5000/signup',{
method: 'POST',
headers: {
'Accept':'application/json',
'Content-type':'application/json'
},
body: JSON.stringify({email_address: email, status: 'subscribed'})
}).then(console.log)
};
when clicking submit I expect the members email address to be sent over to the mailchimp API instead I am getting this error :
Access to fetch at 'http://localhost:5000/signup' 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. If
an opaque response serves your needs, set the request's mode to
'no-cors' to fetch the resource with CORS disabled.
Try this.
app.use((request, response, next) => {
response.header("Access-Control-Allow-Origin", "*");
response.header("Access-Control-Allow-Headers", "Content-Type");
next();
});
to
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "http://localhost:5000");
next();
});
Just enable CORS on your server.
Install cors
$ npm install --save cors
Enable cors in express:
const cors = require('cors');
const express = require('express');
const app = express();
app.use(cors());
More about cors here.
I'm trying to use the code sample from here https://github.com/firebase/functions-samples/blob/master/authorized-https-endpoint/functions/index.js but my cloud function keeps crashing saying
req.headers.split is not a function
at cors (/user_code/index.js:25:37)
at cors (/user_code/node_modules/cors/lib/index.js:188:7)
at /user_code/node_modules/cors/lib/index.js:224:17
at originCallback (/user_code/node_modules/cors/lib/index.js:214:15)
at /user_code/node_modules/cors/lib/index.js:219:13
at optionsCallback (/user_code/node_modules/cors/lib/index.js:199:9)
at corsMiddleware (/user_code/node_modules/cors/lib/index.js:204:7)
at exports.savedProfiles.functions.https.onRequest (/user_code/index.js:14:5)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/providers/https.js:26:41)
at /var/tmp/worker/worker.js:671:7
I'm not sure how else to get it to work. This is the code that I've used so far:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cors = require('cors')({origin: true});
exports.savedProfiles = functions.https.onRequest((req, res) => {
cors(req, res, () => {
if ((!req.headers.authorization || !req.headers.authorization.includes('Bearer '))) {
console.log(req.headers);
console.error('No Firebase ID token was passed as a Bearer token in the Authorization header.');
res.status(403).send('Unauthorized');
return;
}
const tokenId = req.headers.split('Bearer ')[2];
res.status(200).send('Testing');
return;
});
});
I understand that the error is due to req.headers.split('Bearer ')[2]; which simply gets the token from the header. But I think the problem is that req.headers can be a string as well as a string[]. How would I go about getting this to work? Thanks.
req.headers is always an object indexed by the name of the header, never a string. The code you referred to is doing this instead:
req.headers.authorization.split('Bearer ')[1]
It's accessing the "Authorization" header, which is a string, then splitting it.
I have an application in nodejs with jwt authorization, when I send a get from posman the authentication header is found but when I send it from the browser, the authorization header is missing.
Here is the node code, I'm trying to get the authorization header in the verifyToken method, but is not there:
'use strict';
var SwaggerExpress = require('swagger-express-mw');
var app = require('express')();
module.exports = app; // for testing
var _ = require('lodash');
var jwt = require('jsonwebtoken'); // used to create, sign, and verify tokens
var config = {
appRoot: __dirname // required config
};
app.set('superSecret', config.secret); // secret variable
// bootstrap database connection and save it in express context
app.set("models", require("./api/model"));
var a = app.get("models").Role;
var repositoryFactory = require("./api/repository/RepositoryFactory").init(app);
var verifyToken = function (req, res, next) {
// verify token and read user from DB
// var token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiTm9tYnJlVXN1YXJpbyI6ImQiLCJQYXNzd29yZCI6IiQyYSQxMCRYS3BJM2ZDRVFoSzVKUFBQWEdIVVZPbUVPQTZsRVRoZDRtWHl4a0tDeGtUcEhvY0U0UTNILiIsImNyZWF0ZWRBdCI6IjIwMTYtMDktMDVUMTg6Mjk6MTYuMDAwWiIsInVwZGF0ZWRBdCI6IjIwMTYtMDktMDVUMTg6Mjk6MTYuMDAwWiIsInByb2Zlc2lvbmFsSWQiOm51bGwsInByb2Zlc2lvbmFsIjpudWxsLCJpYXQiOjE0NzMyNTczMjcsImV4cCI6MTQ3MzI5MzMyN30.CKB-GiuvwJsDAVnKsWb1FktI9tJY57lSgPRVEfW3pts';
var token = req.headers.authorization;
jwt.verify(token, 'shhhhh', function (err, decoded) {
if (err) {
res.status(403).json({ success: false, message: 'Failed to authenticate token.' });
} else {
// if everything is good, save to request for use in other routes
req.user = decoded;
next();
}
});
};
SwaggerExpress.create(config, function (err, swaggerExpress) {
if (err) { throw err; }
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-CSRF-Token, X-Requested-With, Origin, client-security-token, X-Requested-With, Content-Type, Accept, Authorization");
res.setHeader('Content-Type', 'application/json');
res.setHeader('Access-Control-Allow-Credentials', true);
next();
});
app.use(verifyToken);
// install middleware
swaggerExpress.register(app);
var port = process.env.PORT || 10010;
app.listen(port);
});
I don't know what configuration I'm missing.
The issue was that I was trying to get the authorization token from the OPTIONS method, this method is sent before the actual get, port, put etc, when is a CORS request. So I was trying to get the authorization header from it and it was not there and the method failed.
The solution was to set in the verify token method a validation like this:
if (req.method !== OPTIONS){
}
I think it is easier if you can change the code in verifyToken function : var token = req.headers.authorization; become var token = req.headers.authorization || req.query.access_token || req.body.access_token;
So in the browser, you can add token in "access_token" query param to authenticate in server instead of setting the header.
Hope it is helpful for you !
You need to set those headers in your browser, try use this chrome plugin called ModHeader https://chrome.google.com/webstore/detail/modheader/idgpnmonknjnojddfkpgkljpfnnfcklj
Try adding the following code in .htaccess. Apache removes the Authorization Header. This will ensure it is not removed.
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1