i have this middleware and need to get current user to set it in context of apollo server
app.use(async (req, res, next)=>{
const token = req.headers['authorization'];
if(token !== "null"){
try {
const currentUser = await jwt.verify(token, process.env.SECRET)
req.currentUser = currentUser;
} catch (error) {
console.log(error);
}
}
next()
})
and need to set current user in context
const SERVER = new ApolloServer({
schema,
context:{
currentUser //need to set this current user
}
})
SERVER.applymiddleware({app})
the context api in Apollo server offers handling request as follows
const initGraphQLserver = () => {
const graphQLConfig = {
context: ({ req, res }) => ({
user: req.user,
}),
rootValue: {},
schema,
};
const apolloServer = new ApolloServer(graphQLConfig);
return apolloServer;
};
This will assume that you will have proper middleware that will parse the cookie or header. This is dependent on your authentication mechanism as you need to have set user for the request in some middleware before, if you would like to use JWT, you can use for example this middleware
const auth = (req, res, next) => {
if (typeof req.headers.authorization !== 'string') {
return next();
}
const header = req.headers.authorization;
const token = header.replace('Bearer ', '');
try {
const jwtData = jwt.verify(token, JWT_SECRET);
if (jwtData && jwtData.user) {
req.user = jwtData.user;
} else {
console.log('Token was not authorized');
}
} catch (err) {
console.log('Invalid token');
}
return next();
};
This middleware will inject user if the jwt token is correct, then in your server file you will need to have the following order of middlewares
const app = express();
app.use(auth);
initGraphQLserver().applyMiddleware({ app });
Now you should have user in your schema context. I hope that it is clear, the code is not completed, but should be easy to finalize everything.
Related
I have a very simple login system, which takes a username and a password from a user, and when the form is submitted the data is posted to an express http app.
The login function at the react app is as following:
async function login(event){
event.preventDefault();
setRequestState(REQUEST_STATE.pending)
const request = `${SERVER_URL}/login`;
const formData = new FormData();
formData.append("username",data["username"])
formData.append("password",data["password"])
const response = await fetch(request, {
method: 'POST',
body: formData,
enctype:"multipart/form-data",
credentials: 'same-origin',
})
if(!response.ok){
if(response.status === 401){
console.log("username or password are inccorect")
setDisplayError(response.status)
}
if(response.status === 500){
console.log("internal server error")
setDisplayError(response.status)
}
setRequestState(REQUEST_STATE.no_request)
return;
}
setRequestState(REQUEST_STATE.success)
// navigate("/dashboard")
}
the response is successful consistently when the right data is entered.
I use express-sessions to handle sessions, the setup code for the sessions is as following:
const express = require('express');
const cors = require('cors')
const loginRouter = require('./src/routes/login.js');
const session = require('express-session')
const app = express()
app.use(
cors({
origin:true,
credentials:'same-origin',
}))
app.use(session({
secret: 'test',
resave: false,
saveUninitialized: false,
// store: sessionStore,
cookie: {
secure: false,
sameSite: 'none',
}
}));
app.use(express.urlencoded({extended: true}))
app.use('/login', loginRouter)
app.listen(process.env.PORT ,()=>{
console.log(`Server is listening on port ${process.env.PORT}`)
})
login router is as following:
const express = require('express')
const multer = require('multer')
const router = express.Router()
const {getLogin,postLogin, authenticateLogin} = require('../controllers/login')
upload = multer({})
router.post("/", upload.array(0), authenticateLogin, postLogin)
router.get("/", getLogin)
module.exports=router
login controller is as following:
const {pool} = require('../../sqlconfig')
const bcrypt = require('bcrypt');
const postLogin = (req, res) => {
return res.status(200).send({success:true});
}
const getLogin = (req, res) => {
// console.log("get this login betch")
console.log(req.session.id)
if(req.session.userId){
return res.status(200).send({success:true});
}
// console.log("couldn't get login")
return res.status(401).send({success:false});
}
const authenticateLogin = async (req, res, next) => {
const dict = JSON.parse(JSON.stringify(req.body));
const username = dict["username"];
const password = dict["password"];
if(!dict || !username || !password) {
// console.log("Access deneid")
return res.status(401).send({error:"Access denied"});
}
try{
const query = "SELECT passwrd FROM admins WHERE admins.username=?";
const [result] = await pool.execute(query,[username])
if(result.length <= 0){
return res.status(401).send({error:"Access denied"});
}
const passwordInDB = result[0].passwrd;
if(!passwordInDB){
return res.status(401).send({error:"Access denied"});
}
const match = await bcrypt.compare(password, passwordInDB);
// console.log(match)
if(!match){
return res.status(401).send({error:"Access denied"});
}
const queryUserId = "SELECT id FROM admins WHERE admins.username=?";
const [resultUserId] = await pool.execute(queryUserId,[username])
req.session.userId = resultUserId[0].id;
console.log("logged in successfuly!")
console.log(req.session)
next()
}
catch(e){
console.log(e)
}
}
module.exports={
authenticateLogin,
postLogin,
getLogin
}
when login post is called, a session does get created,
and a cookie is sent back to the react application:
Response
but doesn't get saved:
saved cookies
For this application, I am using React & Express. I have React running on PORT 3000, and Express running on PORT 3001. On the Express side, I have authentication working that uses JWT.
First, here is my auth.js service file:
const jwt = require('jsonwebtoken');
const models = require('../models');
const bcrypt = require('bcryptjs');
var authService = {
signUser: function (user) {
const token = jwt.sign({
Username: user.Username,
UserId: user.UserId
},
'secretkey',
{
expiresIn: '1h'
}
);
return token;
},
verifyUser: function (token) {
try {
let decoded = jwt.verify(token, 'secretkey');
return models.users.findByPk(decoded.UserId);
} catch (err) {
console.log(err);
return null;
}
},
hashPassword: function (plainTextPassword) {
let salt = bcrypt.genSaltSync(10);
let hash = bcrypt.hashSync(plainTextPassword, salt);
return hash;
},
comparePasswords: function (plainTextPassword, hashedPassword) {
return bcrypt.compareSync(plainTextPassword, hashedPassword);
}
}
module.exports = authService;
When a user makes a POST request to the signup route, it works:
router.post('/signup', function (req, res, next) {
models.users.findOrCreate({
where: {
Username: req.body.username
},
defaults: {
FirstName: req.body.firstName,
LastName: req.body.lastName,
Email: req.body.email,
Password: authService.hashPassword(req.body.password)
}
})
.spread(function (result, created) {
if (created) {
res.redirect("http://localhost:3000/login");
} else {
res.send('This user already exist')
}
});
});
Signup works in both Postman and React.
When a user makes a POST request to the login route, it works:
router.post('/login', function (req, res, next) {
models.users.findOne({
where: {
Username: req.body.username
}
}).then(user => {
if (!user) {
console.log('User not found')
return res.status(401).json({
message: "Login Failed"
});
} else {
let passwordMatch = authService.comparePasswords(req.body.password, user.Password);
if (passwordMatch) {
let token = authService.signUser(user);
res.cookie('jwt', token);
res.redirect('http://localhost:3001/users/profile');
} else {
console.log('Wrong Password');
}
}
});
});
Login works in both Postman and React.
When a user makes a GET request to the profile route, it semi-works:
router.get('/profile', function (req, res, next) {
let token = req.cookies.jwt;
if (token) {
authService.verifyUser(token).then(user => {
if (user) {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(user));
} else {
res.status(401);
res.send('Invalid authentication token');
}
});
} else {
res.status(401);
res.send('Invalid authentication token');
}
});
This works only in Postman, I can see the data that I want using Postman. In React, it will not get the profile route that I request. This is where the error comes in: Console Error
On the React side, this is profile GET component:
import React from 'react';
import axios from 'axios';
class UserProfile extends React.Component {
constructor(props) {
super(props);
this.state = {
profileData: []
}
};
fetchProfileData = () => {
var encodedURI = window.encodeURI(this.props.uri);
return axios.get(encodedURI).then(response => {
this.setState(() => {
return {
profileData: response.data
};
});
});
};
componentDidMount() {
this.fetchProfileData();
}
render() {
console.log(this.state.profileData);
if (this.state.profileData.length === 0) {
return <div>Failed to fetch data from server</div>
}
const profile = this.state.profileData.map(user => (
<div key={user.UserId}>Hello world</div>
));
return <div>{profile}</div>
}
}
export default UserProfile;
Then when I go to render this component, I just:
<UserProfile uri="http://localhost:3001/users/profile" />
Which then will render 'Failed to fetch data from server', then the console will log the '401 (Unauthorized)' error. I just can't get it to render in React.
And if anyone wants my Express app.js file for some extra information:
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var models = require('./models');
var cors = require('cors');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hbs');
app.use(function (req, res, next) {
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(cors());
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
models.sequelize.sync().then(function () {
console.log("DB Synced Up");
});
module.exports = app;
Thank you in advanced. I have been struggling to figure this out.
I have tried toying with my UserProfile component. And I've tried toying with my /profile route in Express. The only 2 errors I've gotten is the 401 (Unauthorized) and something about the Headers. I know that my JWT key gets passed onto reacts side, because when I do 'localhost:3000/profile' (react side), I can see that I have the cookie stored. I'm not sure on how to approach authorization on React side. At this point, I am very clueless on what to do. This is the first time I've tried setting up authentication with React. I have always used Express and the .hbs files to render my profile pages. But I've been told that you shouldn't render a profile page in the back-end. So, here I am trying to do it with React.
I have rendered things from the back-end to the front-end, but that's without the use of JWT. I strongly believe that it has something to do with the JWT cookie. I just don't know how to authenticate it in React. Thanks again in advanced.
I fixed it by adding this into my React project:
I added this into my fetchProfileData()
{ withCredentials: true }
fetchProfileData = () => {
var encodedURI = window.encodeURI(this.props.uri);
return axios.get(encodedURI, { withCredentials: true }).then(response => {
this.setState(() => {
return {
profileData: response.data
};
});
});
};
Then in Express, I toyed with my Profile route. Put the data into an array, and sent it on its way:
router.get('/profile', function (req, res, next) {
var userData = [];
let token = req.cookies.jwt;
if (token) {
authService.verifyUser(token).then(user => {
userData.push(user);
res.send(userData);
});
} else {
res.status(401);
res.send('Invalid authentication token');
}
});
Im trying to make a system that it can check it the user is logged of not im using reactjs and JWT tokens that can stored to the cookies in browser.
This is my reactjs file code
const ApproveRequest = (approveOption) => {
if (approveOption === "approve"){
let request = 1;
axios.put("http://localhost:3001/cash/approverequest",{
approved: request,
id: id,
header: { accessToken: cookies.getItem("accessToken") },
withCredentials: true,
}).then((response) => {
if(response.data.error) {
console.log(response.data.error);
}else{
setCashObject({ ...cashObject, request: request });
alert("Request Approve");
}
});
} else {
alert("Field to update the request please contact the dev");
}
}
from my server JWT.js file
const validateToken = (req, res, next) => {
const accessToken = req.header("accessToken");
if(!accessToken) {
return res.json({error: "User not authenticated"});
}
try{
const validToken = verify(accessToken, "bluedragon14S");
if(validToken){
req.authenticated = true;
return next;
}
}catch (err) {
return res.json({error: err});
}
}
from server cash.js route
router.put("/approverequest", validateToken,async (req, res) => {
const { request = 1, id } = req.body;
await Cash.update({request: request}, {where: {id: id} });
res.json(request);
});
I wanted is i want to check if the user is logged in so that he/she can update the request
thank you in advance for your help
Addition in that code i can store the cookies into the browser i just don't know how to check if the user is logged in or not
I think you can access user cookies in this way :
req.cookies.accessToken
so change this :
const accessToken = req.header("accessToken");
to this :
const accessToken = req.cookies?.accessToken
if(accessToken )
...
I'm using Reactjs for my frontend and Springboot and Firebase for my backend. Basically, when a user registers for an account, I will call two APIs to post the account to both Firebase and my Springboot server, the reason being that I need to make use of the user data collected inside my own Springboot server.
The problem I'm facing is that after a user registers for an account, the Springboot API is not called after Firebase API is called. (I call the Firebase API first and then the Springboot one) It seems to me that after calling the Firebase API, everything stops and the code doesn't go any further, thus calling no more API.
How do I make sure that I can call both APIs one after another and then redirect the user to the next page without any interference?
Registration on submit in Reactjs:
const handleOnSubmit=(event: React.FormEvent<HTMLFormElement>)=> {
if (password !== secondPassword) {
setPasswordsMatched(false);
console.log("passwords matched when password!==secondPassword:" + passwordsMatched);
} else if(!username){
setUsernameExists(false);
}else if(!email){
setEmailExists(false);
}else if(!password||!secondPassword){
setPasswordExists(false);
}else{
if(subscribedStatus){
let subscribed:string="subscribed";
firebaseAuthServiceSignUpWithEmailAndPassword(username,email,password,subscribed,handleSignupSuccess);
}else{
let subscribed:string="unsubscribed";
firebaseAuthServiceSignUpWithEmailAndPassword(username,email,password,subscribed,handleSignupSuccess);
}
}
}
//This is the callback function put inside the Firebase API to see if Firebase accepts the registration. If yes, the user is redirected to "/verification-email"
const handleSignupSuccess=(signupStatus:boolean)=>{
setSignupSuccess(signupStatus);
if(signupStatus){
firebaseAuthServiceEmailVerification(setEmailVerificationSent);
navigate("/verification-email");
}
}
Firebase API:
export const firebaseAuthServiceSignUpWithEmailAndPassword= (username:string,email: string, password: string, subscribed:string,callback: (isSuccess:boolean)=>void) =>{
const auth = getAuth();
createUserWithEmailAndPassword(auth, email, password)
.then(async ( userCredential) => {
// Signed in
const user = userCredential.user;
await postAccount(username, email, password, user.uid, subscribed);
callback(true);
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
callback(false);
// ..
});
}
Springboot API:
export const postAccount=(username:string,email:string,password:string,firebaseUid:string,subscribedStatus:string)=>{
axios.post(`http://localhost:8080/user/${username}/${email}/${password}/${firebaseUid}/${subscribedStatus}`
)
.then((res)=>{
}).catch((error)=>{
console.log(error);
})
}
You mostly need a return statement in postAccount function
Quick fix
export const postAccount=(username:string,email:string,password:string,firebaseUid:string,subscribedStatus:string)=>{
// return here
return axios.post(`http://localhost:8080/user/${username}/${email}/${password}/${firebaseUid}/${subscribedStatus}`
)
.then((res)=>{
return res; // may be return here too
}).catch((error)=>{
console.log(error);
})
}
// async function
const handleOnSubmit= async (event: React.FormEvent<HTMLFormElement>)=> {
if (password !== secondPassword) {
setPasswordsMatched(false);
console.log("passwords matched when password!==secondPassword:" + passwordsMatched);
} else if(!username){
setUsernameExists(false);
}else if(!email){
setEmailExists(false);
}else if(!password||!secondPassword){
setPasswordExists(false);
}else{
if(subscribedStatus){
let subscribed:string="subscribed";
// wait till completion
await firebaseAuthServiceSignUpWithEmailAndPassword(username,email,password,subscribed,handleSignupSuccess);
}else{
let subscribed:string="unsubscribed";
// wait till completion
await firebaseAuthServiceSignUpWithEmailAndPassword(username,email,password,subscribed,handleSignupSuccess);
}
}
}
Slightly better fixs:
With multiple API call its better to use async calls
export const firebaseAuthServiceSignUpWithEmailAndPassword = async (username:string, ...) => {
try {
const auth = getAuth();
const userCredentials = await createUserWithEmailAndPassword(auth, email, password)
const user = userCredential.user;
const res = await postAccount(username, email, password, user.uid, subscribed);
// may be some extra checks
//if (res.success) {
// callback(true);
//}
callback(true);
} catch(error: any) {
// handle error
callback(false);
}
}
export const postAccount = async (username: string, ...) => {
return await axios.post(`http://localhost:8080/user/${username}/${email}/${password}/${firebaseUid}/${subscribedStatus}`)
}
Hope it helps in someway
Working locally, my jwt token is invalid but in jwt.io it shows verified signature. Not sure what i am missing. I am having invalid signature whenever i tried to make a call to a api whithin the app.
Link.js
const { Router } = require("express");
const Link = require("../models/Link");
const auth = require("../middleware/auth.middleware");
const router = Router();
router.get("/", auth, async (req, res) => {
try {
const links = await Link.find({ owner: req.user.userId });
res.json(links);
} catch (error) {
res.status(500).json({ message: "Something went wrong, try again" });
}
});
auth.middleware.js
const jwt = require("jsonwebtoken");
const config = require("config");
module.exports = (req, res, next) => {
if (req.method === "OPTIONS") {
return next();
}
try {
const token = req.headers.authorization; // Token
if (!token) {
return res.status(401).json({ message: "No Authorization" });
}
const decoded = jwt.verify(token, config.get("secret"));
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ message: "No Authorization" });
}
};
Links.tsx
const LinksPage: React.FC = () => {
const [links, setLinks] = useState([]);
const fetchLinks = useCallback(async () => {
try {
const fetched = await request("http://localhost:5000/api/link/", "GET", null, {
Authorization: Token,
});
setLinks(fetched);
} catch (error) {
alert(error);
}
}, []);
};
Maybe the "req.headers.authorization" was not what you looking for.
Try to console.log(req.headers.authorization) F12 in chrome, firefox.
I suggest you also POSTMAN (free software). It help me a lot for debugging the back end (server side).
I solved the problem. I had to json.parse(token) which stored in the client in order to jwt.verify(token, secret), but instead i was verifying string that contains object of token and userId.