req.headers.split is not a function when getting token from header - javascript

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.

Related

Sending jwt through header response with resolver mutation GraphQL and Appollo server

Today I'v been trying to send a jwt token back to the client via a header.
Sadly I cant get it to work, my current code looks like this.
the resolver/mutation
// log user in
Login: async(parent, args, context, info) =>{
const LoginUser = await user.findOne({username: args.username})
if (LoginUser.password == args.password){
//loginsucces
const token = jwt.sign({id: user.id}, process.env.TOKEN_SECRET);
context.res.header = ("auth", token)
LoginUser.token = token;
return LoginUser;
}else{
return LoginUser;
}
}
app.js
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ res }) => ({
res
})
});
For some reason the code doesnt recognise .header so it wont send the token. Currently i send it via the token field form the user. But that takes up space in my mongodb, and destroys the whole purpose of jwt.
Am I just forgetting a function or not getting it?

How do i store jsonwebtoken on cookie for front-end so client can send back the token for auth

I've been struggling to do this for about 6 days...
Everything is working perfectly such as authorization but one problem I had is making authentication.
On my user model (for creating the database schema) I do have a way to generate a token for logged in users or registered.
userSchema.methods.generateAuthToken = function(){
const token = jwt.sign({ _id: this._id }, config.get('jwtPrivateKey'));
return token;
}
So when user post to /login, server will respond with a token:
router.post('/', async (req, res) =>{
// Here i'm validating data and then if everything is right the code under will run.
console.log('logged in as: ' + user.username);
// Here i'm using the function to generateAuthToken().
const token = user.generateAuthToken();
console.log("Token from server: " + token);
// now here is my main problem i would like to use cookies to store it for an hour or so.
// then client can send it back to server for protected route.
res.status(200).send(token);
});
I have made a middleware function for auth (to check the token if you're going through a protected route)
module.exports = function (req, res, next){
// instead of using headers i would like to check for the cookie value if it's the token,
// pass the user in, else Access denied.
// I have no idea how to use cookie parser with middleware functions.
const token = req.header('x-auth-token');
if(!token) return res.status(401).send('Access denied. Sign in or register.');
try{
const decoded = jwt.verify(token, config.get('jwtPrivateKey'));
req.user = decoded;
next();
}
catch(err){
res.status(400).send('Invalid Token!');
}
}
here i'm using the auth middleware function:
const express = require('express');
const router = express.Router();
const auth = require('../middleware/auth');
// but it's actually not passing the user in since i haven't done it with cookies.
router.get('/', auth, (req, res) =>{
res.render('index', {});
});
I do know I can do it with localStorage but it's a terrible practice and it would be better to store it on cookies so no one could hack on.
Is there any good approach to solve this problem? I'm kinda lost and lost hope to go back to sessionID (which I don't want to :( ).
After you request on frontend, you need get the response (token) and save on browser using this for example:
fetch('http://your-api-host/login', {
method: 'POST',
body: {
username: "user1",
password: "passworduser"
}
})
.then((res) => res.text((res)))
.then((token) => {
document.cookie = `AUTH_API=${token}`; <-- this save the cookie
})
With this value saved on frontend you need send this information on all requests, it's commum send this value on your HEADER (how you makes), to save on header you need read the value from token and put on header, like this:
const headersTemp = document.cookie.split(';'); // <-- this get all cookies saves and splits them in the array.
const finalHeaders = {};
headersTemp.forEach((header) => { // <-- looping on all cookies
const headerTemp = header.split('='); // <-- split each cookie to get key and value
finalHeaders[headerTemp[0].trim()] = headerTemp[1].trim() // <-- save on object to access using keys.
})
Now you can access all cookies using the key (the same used before), I used the key AUTH_API to save my cookie, let's send the request using fetch api:
fetch('http://your-api-host/route-protected', {
method: 'POST',
headers: {
'x-auth-token': finalHeaders['AUTH_API']
},
})
If you creating your application using libraries how React or any SPA framework, probably you will use tools like Axios, and I recommend uses libraris how This, it's more easy to work with cookies.

Getting authentication error on axios while passing basic authentication credentials

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.

.put method for updating password in mocha chai requests not working

My code:
const model = require('../db/models/user');
const describe = require('mocha').describe;
const assert = require('chai').assert;
const chaiHttp = require('chai-http');
let chai = require('chai');
let server = require('../server');
chai.use(chaiHttp);
describe('Test user registration, login, update password', () => {
beforeEach((done) => {
// Reset user mode before each test
model.User.remove({}, (err) => {
console.log(err);
done();
})
});
Now, I get the error
UnhandledPromiseRejectionWarning: TypeError: Cannot read property
'_id' of null
in the route itself, specifically:
router.put('/me/update-password', async (req, res, next) => {
const {body} = req;
const auth = req;
const userId = auth._id; // problem on this line!
// rest of code...
});
So, after registration and logging in (which works fine, as it should!), I am having a lot of problems to update the password. In the params I am sending generated token and in the body is the password field with new password. On live example (for example Postman) it works as it should, but in tests it simply does not.
I really have no idea and have lost a lot of my time over this already (3 days).
Can someone please take a look suggest solution?
Much appreciated.
Updated with auth.js:
const jwt = require('jsonwebtoken');
const isAu = function(req) {
return jwt.verify(req.headers.authorization.split(' ')[1], 'secret', function (err, decoded) {
if (err) {
return null;
}
return decoded;
});
};
module.exports = isAu;
EDIT:
Since OP changed the original question after it has been answered here is the link to original: https://stackoverflow.com/revisions/55064109/1
=======================================
JWT verify method accepts Authorization token - you are fetching that correctly by splitting Authorization header string in order to fetch token.
HTTP Authorization header string hold Authentication scheme type (Bearer, Basic, Digest, etc) and the token value
Authorization: Bearer eyJhbGciOiJIUzI1NiIXVCJ9...TJVA95OrM7E20RMHrHDcEfxjoYZgeFONFh7HgQ
but your Authorization header in the Chai request only holds the value of the token and not the Authentication scheme type.
Assumin your Authentication scheme is Bearer you need to set that in your Chai request Authorization header:
...
chai.request(server)
.put('/api/me/update-password')
.set('Authorization', `Bearer ${token}`)
.send(`${updatedPassword}`)
.end((error, response) => {
assert.equal(response.status, 200);
done();
});
...
On the other hand, in case you do not specify Authentication type in the request authorization header than you should send it like that to JWT to veirfy:
const isAuthenticated = function(req) {
return jwt.verify(req.headers.authorization, 'secret', function (err, decoded) {
if (err) {
return null;
}
return decoded;
});
};

cors error within a cloud function

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.

Categories