How to add authorization header to a react download button? - javascript

I have a button where the logged user can download a file which is stored in the database and is fetched by react from node js/express js. Without authentication I can easily do that by just an tag. But with authentication I am struggling a lot.
React:
const handleDownload = async () => {
const result = await fetch (process.env.REACT_APP_BACKEND_URL + `/files/download/${props.id}`, {
headers: {'Authorization': auth.token}
});
const responseData = await result.json();
return responseData;
}
return (
<button onClick={handleDownload}>Download File</button>
)
Express js:
router.get('/download/:fid', filesControllers.downloadFile);
const downloadFile = async (req, res, next) => {
const fileId = req.params.fid;
let filePost;
try {
filePost = await File.findById(fileId);
} catch (err) {
return next(new HttpError("Error", 500));
}
console.log(filePost.file);
res.download(filePost.file);
};

possible solutions:
verify that your auth.token really contains the token (try a
console.log for example)
if you're using a bearer token do this: {'Authorization': "Bearer
"+auth.token}

from backend side you will need to do some changes learn jwt authentication or anyother authentication technique for backend and make sure that it works with frontend
how this app will work is when user is not logged in and he clicks on download backend verifies the token and if token is invalid or doesnt exist it send the error through resopnse and frontend shows it on ui

Related

how to get cookie in react passed from express js api (MERN stack)

I have an api in express js that stores token in cookie on the client-side (react). The cookie is generated only when the user logins into the site. For example, when I test the login api with the postman, the cookie is generated as expected like this:
But when I log in with react.js then no cookie is found in the browser. Looks like the cookie was not passed to the front end as the screenshot demonstrates below:
As we got an alert message this means express api is working perfectly without any error!!
Here is my index.js file on express js that includes cookie-parser middleware as well
require("dotenv").config();
const port = process.env.PORT || 5050;
const express = require("express");
const app = express();
const cors = require("cors");
const authRouter = require("./routes/auth");
var cookieParser = require('cookie-parser')
connect_db();
app.use(express.json());
app.use(cookieParser())
app.use(cors());
app.use("/" , authRouter);
app.listen(port , () => {
console.log("Server is running!!");
})
Code for setting up the cookie from express api only controller
const User = require("../models/user");
const jwt = require("jsonwebtoken");
const bcrypt = require('bcrypt')
const login = async (req, res) => {
const { email, password } = req.body;
try {
const checkDetails = await User.findOne({ email });
if (checkDetails) {
const { password: hashedPassword, token, username } = checkDetails;
bcrypt.compare(password, hashedPassword, function (err, matched) {
if (matched) {
res.cookie("token", token, { expires: new Date(Date.now() + (5 * 60000)) , httpOnly: true }).json({ "message": "You logged in sucessfully!" });
} else {
res.status(500).json({ "message": "Wrong password" });
}
});
} else {
res.status(500).json({ "message": "Wrong email" });
}
} catch (error) {
console.log(error.message);
}
}
Here is the react.js code that I am using to fetch data from api without using a proxy in package.json file
if (errors.length === 0) {
const isLogin = await fetch("http://localhost:5000/api/login", {
method: "POST",
body: JSON.stringify({ email, password }),
headers: {
"Content-Type": "application/json"
}
});
const res = await isLogin.json();
if(res) alert(res.message);
}
I want to get to know what is the reason behind this "getting cookie in postman but not in the browser". Do I need to use any react package?
The network tab screenshot might help you.
If I see in the network tab I get the same cookie, set among the other headers
To my understanding, fetch doesn't send requests with the cookies your browser has stored for that domain, and similarly, it doesn't store any cookies it receives in the response. This seems to be the expected behaviour of fetch.
To override this, try setting the credentials option when making the request, like so:
fetch(url, {
// ...
credentials: 'include'
})
or, alternatively:
fetch(url, {
// ...
credentials: 'same-origin'
})
You can read more about the differences between the two here.
I got my error resolved with two changings in my code
In front end just added credentials: 'include'
fetch(url, {
method : "POST"
body : body,
headers : headers,
credentials: 'include'
})
And in back end just replaced app.use(cors()); to
app.use(cors({ origin: 'http://localhost:3000', credentials: true, exposedHeaders: ['Set-Cookie', 'Date', 'ETag'] }))
That's it got resolved, Now I have cookies stored in my browser!!! Great. Thanks to this article:
https://www.anycodings.com/2022/01/react-app-express-server-set-cookie-not.html
during development i also faced same things, let me help you that how i solve it,
Firstly you use proxy in your react package.json, below private one:-
"private": true,
"proxy":"http://127.0.0.1:5000",
mention the same port on which your node server is running
Like:-
app.listen(5000,'127.0.0.1',()=>{
console.log('Server is Running');
});
above both must be on same , now react will run on port 3000 as usual but now we will create proxy to react So, react and node ports get connected on same with the help of proxy indirectly.
Now, when you will make GET or POST request from react then don't provide full URL, only provide the path on which you wants to get hit in backend and get response,
Example:-
React side on sending request, follow like this:-
const submitHandler=()=>{
axios.post('/api/loginuser',
{mobile:inputField.mobile,password:inputField.password})
.then((res)=>{
console.log(res);
})
.catch((err)=>{
console.log(err);
})
}
Node side where it will hit:-
app.post('/api/loginuser', async(req,res)=>{
//Your Code Stuff Here
res.send()
}
on both side same link should hit, it is very important
it will 100%.
don't forget to mention
on node main main where server is listening

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?

TypeError: jwt.split is not a function at OAuth2Client.verifySignedJwtWithCertsAsync Node package: google-auth-library

The react-google-login from the client react app sends the response back to the Nodejs server with a post request-
client code -
import axios from 'axios';
import React, { Component } from 'react';
import GoogleLogin from 'react-google-login';
import refreshTokenSetup from '../../utils/refreshToken';
const clientId =
'xxxxxx-xfdgsdjg3gfxxxxxxxxxxx.apps.googleusercontent.com';
function Login() {
const onSuccess = (res) => {
console.log('Login Success: currentUser:', res.profileObj);
alert(
`Logged in successfully welcome ${res.profileObj.name} 😍. \n See console for full profile object.`
);
axios
.post('http://localhost:5000/auth/checkToken', { body: res.tokenId })
.then()
.catch((err) => {
console.log(err);
});
};
const onFailure = (res) => {
console.log('Login failed: res:', res);
alert(
`Failed to login. 😢 Please ping this to repo owner twitter.com/sivanesh_fiz`
);
};
return (
<div>
<GoogleLogin
clientId={clientId}
buttonText='Login'
onSuccess={onSuccess}
onFailure={onFailure}
cookiePolicy={'single_host_origin'}
style={{ marginTop: '100px' }}
isSignedIn={true}
/>
</div>
);
}
export default Login;
the backend route-
const { OAuth2Client } = require('google-auth-library');
const key = require('../config/key');
module.exports = {
checkToken: (req, res, next) => {
console.log('checking begins...', req.body);
const client = new OAuth2Client(key.GOOGLE_CLIENT_ID);
async function verify() {
const ticket = await client.verifyIdToken({
idToken: req.body,
audience: key.GOOGLE_CLIENT_ID, // Specify the CLIENT_ID of the app that accesses the backend
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
});
const payload = ticket.getPayload();
const userid = payload['sub'];
// If request specified a G Suite domain:
// const domain = payload['hd'];
}
verify().catch(console.error);
},
};
The above code is in reference to official Google Documentation available at- https://developers.google.com/identity/sign-in/web/backend-auth
Now everything works fine, user is signed in in the client side, the tokenId is sent back to the server and can be verified by console logging it, even on https://jwt.io/ but the following error is shown-
TypeError: jwt.split is not a function
at OAuth2Client.verifySignedJwtWithCertsAsync (E:\Projects\EAbackend\node_modules\google-auth-library\build\src\auth\oauth2client.js:528:30)
at OAuth2Client.verifyIdTokenAsync (E:\Projects\EAbackend\node_modules\google-auth-library\build\src\auth\oauth2client.js:394:34)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
at async verify (E:\Projects\EAbackend\middleware\auth.js:9:22)
The main issue is that the example from Google doesn't really tell us what is expected as input to the verifyIdToken({options}) function.
This is what Google stated:
After Google returns an ID token, it's submitted by an HTTP POST method request, with the parameter name credential, to your login endpoint.
Which to me, is a little unclear of what is actually sent to the server to be verified. So here is a working example, copied/pasted from https://developers.google.com/identity/gsi/web/guides/verify-google-id-token, but with proper definitions for token and CLIENT_ID that are not mentioned on the Google site.
Server side Node JS:
exports.googleTokenChecker = (request, response) => {
const CLIENT_ID = request.body.clientId;
const token = request.body.credential;
// copied from Google example
const {OAuth2Client} = require('google-auth-library');
const client = new OAuth2Client(CLIENT_ID);
async function verify() {
const ticket = await client.verifyIdToken({
idToken: token,
audience: CLIENT_ID, // Specify the CLIENT_ID of the app that accesses the backend
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
});
const payload = ticket.getPayload();
const userid = payload['sub'];
// If request specified a G Suite domain:
// const domain = payload['hd'];
}
verify().catch(console.error);
}
Client side HTML to show what is sent to the backend:
<div id="g_id_onload"
data-client_id="CLIENT_ID.apps.googleusercontent.com"
data-callback="handleCredentialResponse"
data-auto_prompt="false">
</div>
<script>
function handleCredentialResponse(response) {
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:3000/api/google_token_checker", true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(response));
}
</script>
// contents of response parameter
// {
// clientId: 'CLIENT_ID.apps.googleusercontent.com',
// credential: 'JWT_HEADER.JWT_PAYLOAD.JWT_SIGNATURE',
// select_by: 'btn'
// }
Problem is in the idToken: req.body,
req.body has a body object in which the token was present, simply changing it to req.body.body solved the error.
The problem might be very begginner level but took a lot of my time and no online resourse was available which could point me in any direction.
Check the POST Request you will find the error.

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.

.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;
});
};

Categories