I am trying to learn backend and frontend and so far I was okey. But now I have a problem.
In my backend which I use Express, I send cookie with res.cookie as shown below
auth.js (/auth route)
const register = asyncErrorWrapper(async (req, res, next) => {
const {name, email, password} = req.body;
const user = await User.create({
name,
email,
password
});
sendJwtToClient(user, res);
});
tokenHelper.js
const sendJwtToClient = (user, res) => {
// user modeline gore jwt olusturma
const token = user.generateJwtFromUser();
const {JWT_COOKIE, NODE_ENV} = process.env;
return res
.status(200)
.cookie('access_token', token, {
httpOnly:true,
expires: new Date(Date.now() + parseInt(JWT_COOKIE) * 1000 * 60),
secure: NODE_ENV === 'development' ? false : true
})
.json({
success: true,
access_token: token,
data: {
name: user.name,
email: user.email
},
message: 'Your registration is succesful!'
});
};
Then I get response with Angular app in register.component which is connected with Auth Service
from Register.component
registerUser() {
this.sendButton.nativeElement.disabled = true;
this._authService.registerUser(this.user).subscribe(
res => {
if(res.status === 200) {
console.log(res);
}
}, err => {
console.log(err);
this.showError.nativeElement.className += ' alert alert-danger';
this.errMessage = err.error.message;
this.sendButton.nativeElement.disabled = false;
})
}
Auth Service
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class AuthService {
_registerURI = 'http://localhost:3000/auth/register';
_loginURI = 'http://localhost:3000/auth/login';
constructor(private http: HttpClient) { }
registerUser(user) {
return this.http.post<any>(this._registerURI, user, {observe: 'response'});
};
loginUser(user) {
return this.http.post<any>(this._loginURI, user, {observe: 'response'});
};
};
Well I get this response when I click register button: response
Btw I use cors package on backend. Thanks in advance.
I can also share other codes, pages whatever you want. I just thought that this is enough.
Okey, got it.
The problem occurred because of the cors policy. To send cookies to the front-end you need to let in cors package.
Here is the options of cors package that used by express:
app.use(cors( {
origin: 'http://localhost:4200',
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
} ));
And this is the Auth Service method posting registration
registerUser(user) {
return this.http.post<any>(this._registerURI, user, {observe: 'response', withCredentials: true});
};
Related
I have been struggling for days with no success with my web application. The kind of similar questions on stake overflow are not addressing my issue. I have a standalone Nodejs server which is basically an API provider and a standalone Nextjs web app which serves as the UI. The idea is to have them different Servers in production. On localhost the Next app is running on port 3000 and the Node App is running on port 5000. I am trying to do authentication but how can I verify cookies from the browser (Nextjs App) on the Server. When a user logs in the server sends the user data and a token in a cookie. The cookie is then saved on the browser, from here how can I send back that cookie to the server for verification from Nextjs.
Here is my backend code (Nodejs App running at port 5000)
const signToken = (id) =>
jwt.sign({ id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_IN,
});
const createSendToken = (user, statusCode, res) => {
const token = signToken(user._id);
const cookieOptions = {
expires: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000),
httpOnly: true,
};
if (process.env.NODE_ENV === 'production') cookieOptions.secure = true;
res.cookie('jwt', token, cookieOptions);
// Remove password from output
user.password = undefined;
user.passwordChangedAt = undefined;
res.status(statusCode).json({
status: 'success',
token,
data: {
user,
},
});
};
exports.Login = catchAsync(async (req, res, next) => {
const { email, password } = req.body;
//1) Check if email and password exists
if (!email || !password) {
return next(new AppError('Please provide email and password!', 400));
}
//2) Check if user exists && password is correct
const user = await User.findOne({ email }).select('+password');
if (!user || !(await user.correctPassword(password, user.password))) {
return next(new AppError('Incorrect email or password', 401));
}
//3) If everything is ok , send token to client
createSendToken(user, 200, res);
});
Frontend Code (Nextjs App running on port 3000)
const postData = async (url, post, token) => {
try {
const res = await fetch(`${serverUrl}${url}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: token,
},
body: JSON.stringify(post),
});
const data = await res.json();
return data;
} catch (error) {
return error;
}
};
const handleSubmit = async(e) => {
e.preventDefault();
const res = await postData('users/login', userData);
console.log(res);
if(res.status === 'fail') return errorAlert(res.message)
successAlert('Login Successful')
dispatch({ type: 'AUTH', payload: {
token: res.token,
user: res.user,
cookie: cookieData
}})
Cookie.set('token', res.token)
localStorage.setItem('firstLogin', true)
};
The cookie which you set after authentication in the browser is included by the browser for subsequent requests to Nextjs server automatically.
You can verify this by checking the req/res in Network Tab.
In Nextjs, we can have a function for validation:
import { NextRequest } from 'next/server'
const TOKEN = '<token-name>';
const isRequestValid = async (req: NextRequest) => {
const cookie = req.cookies[TOKEN]
if (!cookie) {
return false;
}
// validate cookie here by sending request to Node server (running on 5000)
// and return true or false
return ...
}
Then create a _middleware.tsx under pages. Here we will define our _middleware function.
This would run on every req (since it's defined at the root level. More on here)
import { NextRequest, NextResponse } from 'next/server'
export function middleware(req: NextRequest) {
const { pathname } = req.nextUrl;
// check for authenticated routes
if (pathname === '<path-for-which-you-want-to-authenticate>') {
if (!isRequestValid(req)) {
return NextResponse.redirect('<url-for-redirection-if-not-authenticated>')
}
}
// if user is authenticated this will be called which continues the
// normal flow of application
return NextResponse.next()
}
I am writing a front-end app and I got stuck on setting token as a cookie in browser.
Back-end is hosted in heroku and front-end is running on localhost
My request from React:
export const axiosConfig: AxiosRequestConfig = {
withCredentials: true,
headers: {
"content-type": "application/json"
},
};
export const login = async (data: UserLoginData): Promise<UserLoginResponse> => {
const res = await axios.post<UserLoginResponse>(API_URL + "/login", data, axiosConfig);
return res.data;
};
Express setup:
app.use(cookieParser());
app.use(cors({ credentials: true, origin: "http://localhost:3000" }));
///
...
///
export const login = async (request: Request, res: Response, next: NextFunction): Promise<void> => {
try {
if (isBodyEmpty(request))
throw new Error();
const { email, password } = request.body;
if (!(email && password)) {
sendFailResponse(res, 400, "All input is required");
return;
}
const user = await User.findOne({ email });
if (user && (await bcrypt.compare(password, user.password))) {
user.token = await createToken(user._id, email);
res.cookie("token", user.token, { maxAge: 900000, httpOnly: true });
res.status(200).send(constructResponse("Success", user));
return next();
}
sendFailResponse(res, 400, "Invalid Credentials");
} catch (error) {
sendFailResponse(res, 400, error.message);
}
};
Response from postman:
Back-end repo: https://github.com/simsta6/botique
Based on the Axios documentation the axios config for credentials is withCredentials: true instead of credentials: 'include' (as defined for fetch). I did not confirm this is the cause of your issue.
Note that you cannot access cookies in the front-end if you set them with the HttpOnly option (MDN).
Note that you can never access the Set-Cookie header on the client-side. This header is listed as forbidden and is filtered out by web-browsers.
I moved from a express server handling my API's in Next to their built in API Routes!
Loving it!
Anyway, I am using Passport.js for authentication and authorization and have implemented that successfully.
But I noticed a few things which I want to bring first as I am pretty sure they're related to the problem with the SWR hook:
In my login route: /api/login:
import nextConnect from 'next-connect'
import auth from '../../middleware/auth'
import passport from '../../lib/passport'
import connectDB from '../../lib/mongodb';
const handler = nextConnect()
handler
.use(auth)
.post(
async (req, res, next) => {
await connectDB();
passport.authenticate('local', (err, user, info) => {
if (err) { return errorHandler(err, res) }
if (user === false) {
return res.status(404).send({
msg: `We were unable to find this user. Please confirm with the "Forgot password" link or the "Register" link below!`
})
}
if (user) {
if (user.isVerified) {
req.user = user;
return res.status(200).send({
user: req.user,
msg: `Your have successfully logged in; Welcome to Hillfinder!`
});
}
return res.status(403).send({
msg: 'Your username has not been verified! Check your email for a confirmation link.'
});
}
})(req, res, next);
})
export default handler
You can see I'm using the custom callback in Passport.js so when I get the user from a successful login I am just assigning the user to req.user = user
I thought this should allow the SWR hook to always return true that the user is logged in?
This is my hooks.js file i.e. SWR functionality:
import useSWR from 'swr'
import axios from 'axios';
export const fetcher = async (url) => {
try {
const res = await axios.get(url);
console.log("res ", res);
return res.data;
} catch (err) {
console.log("err ", err);
throw err.response.data;
}
};
export function useUser() {
const { data, mutate } = useSWR('/api/user', fetcher)
// if data is not defined, the query has not completed
console.log("data ", data);
const loading = !data
const user = data?.user
return [user, { mutate, loading }]
}
The fetcher is calling the /api/user:
import nextConnect from 'next-connect'
import auth from '../../middleware/auth'
const handler = nextConnect()
handler
.use(auth)
.get((req, res) => {
console.log("req.user ", req.user); // Shouldn't the req.user exist??
res.json({ user: req.user })
})
export default handler
Shouldn't that always return the user from a successful login?
Lastly here is my LoginSubmit:
import axios from 'axios';
export default function loginSubmit(
email,
password,
router,
dispatch,
mutate
) {
const data = {
email,
password,
};
axios
.post(`/api/login`,
data, // request body as string
{ // options
withCredentials: true,
headers: {
'Content-Type': 'application/json'
}
}
)
.then(response => {
const { userId, user } = response.data
if (response.status === 200) {
setTimeout(() => {
router.push('/profile');
}, 3000);
dispatch({ type: 'userAccountIsVerified' })
mutate(user)
}
})
}
Any help would be appreciated!
Update
Added auth middleware to question:
import nextConnect from 'next-connect'
import passport from '../lib/passport'
import session from '../lib/session'
const auth = nextConnect()
.use(
session({
name: 'sess',
secret: process.env.TOKEN_SECRET,
cookie: {
maxAge: 60 * 60 * 8, // 8 hours,
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
path: '/',
sameSite: 'lax',
},
})
)
.use((req, res, next) => {
req.session.users = req.session.users || []
next()
})
.use(passport.initialize())
.use(passport.session())
export default auth
Added: serializeUser && deserializeUser functions;
import passport from 'passport'
import LocalStrategy from 'passport-local'
import User from '../models/User'
passport.serializeUser((user, done) => {
done(null, user._id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
passport.use(
new LocalStrategy(
{ usernameField: 'email', passwordField: 'password', passReqToCallback: true },
async (req, email, password, done) => {
try {
const user = await User.findOne({ email }).exec();
if (!user) {
return done(null, false, { message: 'Invalid username!' });
}
const passwordOk = await user.comparePassword(password);
if (!passwordOk) {
return done(null, false, {
message: 'Invalid password!'
});
}
return done(null, user);
} catch (err) {
return done(err);
}
}
)
);
export default passport
And this is the session.js file:
import { parse, serialize } from 'cookie'
import { createLoginSession, getLoginSession } from './auth'
function parseCookies(req) {
// For API Routes we don't need to parse the cookies.
if (req.cookies) return req.cookies
// For pages we do need to parse the cookies.
const cookie = req.headers?.cookie
return parse(cookie || '')
}
export default function session({ name, secret, cookie: cookieOpts }) {
return async (req, res, next) => {
const cookies = parseCookies(req)
const token = cookies[name]
let unsealed = {}
if (token) {
try {
// the cookie needs to be unsealed using the password `secret`
unsealed = await getLoginSession(token, secret)
} catch (e) {
// The cookie is invalid
}
}
req.session = unsealed
// We are proxying res.end to commit the session cookie
const oldEnd = res.end
res.end = async function resEndProxy(...args) {
if (res.finished || res.writableEnded || res.headersSent) return
if (cookieOpts.maxAge) {
req.session.maxAge = cookieOpts.maxAge
}
const token = await createLoginSession(req.session, secret)
res.setHeader('Set-Cookie', serialize(name, token, cookieOpts))
oldEnd.apply(this, args)
}
next()
}
}
so I have tested my API route /getUser using Postman which is receiving data successfully being received in json format. However the Jwt token which is stored in localstorage and past in the headers is not being verified as within my browser I receive 'Access denied. No JWT provided' which is sent with a 401 status.
Nodejs API is below this includes my authentication route and /getUserwhich users a middleware file found below also which verifies the token
const express = require('express');
const users = express.Router();
const cors = require('cors');
const moment = require('moment');
const jwt = require('jsonwebtoken');
// var exjwt = require('express-jwt');
const auth = require('../middleware/auth');
const bcrypt = require('bcrypt');
const Sequelize = require('sequelize');
const bodyParser = require('body-parser');
const User = require('../models/User');
const config = require('config');
// const secret = 'dassdfdd';
users.use(
bodyParser.urlencoded({
extended: false
})
);
users.use(bodyParser.json());
users.use(cors());
users.post('/authenticate', (req, res) => {
User.findOne({
where: {
email: req.body.email
}
}).then(user => {
if (user) {
if (bcrypt.compareSync(req.body.password, user.password)) {
const payload = {
id: user.id,
name: user.first_name
};
var token = jwt.sign(payload, config.get('secret'), {
expiresIn: 1440 // expires in 24 hours
});
// res.cookie('auth', token);
res.cookie('jwt', token, { httpOnly: true, secure: true });
// return the information including token as JSON
// // res.setHeader('token', token);
// res.setHeader('Authorization', 'Bearer ' + token);
res.send({
message: 'authentication done ',
token: token,
user: user.toJSON()
});
console.log(token);
console.log('Successful Login');
console.log(user.first_name);
} else {
res.json({ message: 'please check your password !' });
console.log('incorrect password');
}
} else {
res.json({ message: 'user not found !' });
console.log('user cannot be found');
}
});
});
users.get('/protected', (req, res) => {
res.send('protected');
});
users.get('/getUser', auth, function(req, res) {
// const currentUser = req.;
// const id = parseInt(req.params.id);
const users = User.findOne({
where: { id: req.user.id }
});
// }
// });
// if (!users) {
// return res.status(404).send('Cannot find your team players');
// }
console;
res.status(200).json(users);
});
module.exports = users;
Login Component
import React, { Component } from 'react';
import axios from 'axios';
import { withRouter } from 'react-router-dom';
class Login extends Component {
constructor() {
super();
this.state = {
email: '',
password: '',
errors: {}
};
this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
onChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
onSubmit(e) {
e.preventDefault();
// const user = {
// email: this.state.email,
// password: this.state.password
// };
axios
.post('http://localhost:5000/api/authenticate', {
email: this.state.email,
password: this.state.password
})
.then(res => {
localStorage.setItem('token', res.data.token);
this.props.history.push('/Profile');
});
}
auth.js this is my middleware file
const jwt = require('jsonwebtoken');
const config = require('config');
module.exports = function(req, res, next) {
const token = req.header('Authorization');
if (!token) {
return res.status(401).send('Access denied. No JWT provided.');
}
try {
const decoded = jwt.verify(token, config.get('secret'));
res.set('Authorization', token);
req.user = decoded;
next();
} catch (ex) {
res.status(400).send('Invalid JWT.');
}
};
Profile Component( this is the page , i want the users data to appear)
import React, { Component } from 'react';
import { getJwt } from '../helpers/jwt';
import axios from 'axios';
import { withRouter } from 'react-router-dom';
class Profile extends Component {
constructor(props) {
super(props);
this.state = {
// users: []
};
}
componentDidMount() {
const jwt = getJwt();
if (!jwt) {
this.props.history.push('/Login');
}
axios
.get('http://localhost:5000/api/getUser', {
headers: { Authorization: `Bearer ${jwt}` }
})
.then(res => {
this.profile = res.data;
console.log('profile is:', res.data);
})
.catch(error => console.log(error));
}
Inside your auth.js middleware file, you have const token = req.header('Authorization');. This includes the Bearer prefix which is not part of the JWT itself, and will need to be removed before the token can be parsed by the JWT library.
The Bearer prefix identifies the token type as a Bearer token under the OAuth 2.0 Authorization Framework. If you wish to support other token types the prefix will be different to identify the respective type and framework.
I need to pass a JWT with GET, POST, PUT, DELETE methods from my UI to my API.
For this I tried to create a front-end using ReactJS. I get:
POST http://localhost:4000/book/ 401 (Unauthorized)
when I try to send a request.
I know I have to pass my token, which I did via postman when I send data to mongo DB.
I'm new to MERN stack, so I am still trying to understand.
This is the book controller
import mongoose from 'mongoose';
import { book_schema } from '../model/book_model';
const Book = new mongoose.model('Book', book_schema);
export const add_new_book = (req, res) => {
let new_book = new Book(req.body);
new_book.save((err, book) => {
if (err) {
res.send(err);
}
res.json(book);
});
};
These are the book routes
import {add_new_book} from '../controller/book_controller';
import {authorization} from '../controller/user_controller';
const book_routes = (app) => {
//GET
app.route('/book')
//POST
.post(authorization,add_new_book);
}
This is the user controller
import mongoose from 'mongoose';
import bycrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { user_schema } from '../model/user_model';
const User = mongoose.model('User', user_schema);
export const register = (req, res) => {
const new_user = new User(req.body);
new_user.hash_password = bycrypt.hashSync(req.body.password, 10);
new_user.save((err, user) => {
if (err) {
return res.status(400).send({
message: err
});
} else {
user.hash_password = undefined;
return res.json(user);
}
});
};
export const authenticate = (req, res) => {
User.findOne({
user_name: req.body.user_name
}, (err, user) => {
if (err) throw err;
if (!user) {
res.status(401).json({ message: 'Authentication Failed ! - No User.' })
} else if (user) {
if (!user.comparePassword(req.body.password, user.hash_password)) {
res.status(401).json({ message: 'Authentication Failed ! - Wrong Password.' })
} else {
var token = res.json({
token: jwt.sign({ user_name: user.user_name }, 'RESTFULAPIs', { expiresIn: '24h' })
});
//$window.sessionStorage.accessToken = response.body.access_token;
return token;
}
}
});
};
export const authorization = (req, res, next) => {
if (req.user) {
next();
} else {
return res.status(401).json({ message: 'Unauthorized User !' })
}
};
export const de_authenticate = (req, res) => {
};
This is the server
import express from 'express';
import cors from 'cors';
import bodyParser from 'body-parser';
import mongoose from 'mongoose';
import jsonwebtoken from 'jsonwebtoken';
import db_config from './config_db.js';
const app = express();
const PORT = 4000;
//import routes here
import book_routes from './api/route/book_route';
import user_routes from './api/route/user_route';
import item_routes from './api/route/item_route';
//mongo DB connection
mongoose.Promise = global.Promise;
mongoose.connect(db_config.DB, { useNewUrlParser: true }).then(
() => { console.log('Database is connected') },
err => { console.log('Can not connect to the database' + err) }
);
app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
//JWT setup
app.use((req, res, next) => {
if (req.headers && req.headers.authorization && req.headers.authorization.split(' ')[0] === 'JWT') {
jsonwebtoken.verify(req.headers.authorization.split(''), [1], 'RESTFULAPIs', (err, decode) => {
if (err) req.user = undefined;
req.user = decode;
//console.log(req.user );
//console.log(decode);
next();
});
} else {
req.user = undefined;
next();
}
});
//to app
book_routes(app);
user_routes(app);
item_routes(app);
app.get('/', (req, res) =>
res.send(`Node Server and Express Server Running on Port : ${PORT}`)
);
app.listen(PORT, function () {
console.log(`Server is running on Port: ${PORT}`);
});
I developed front end using ReactJS and import axios for access API via URL. In insert_book.js file my form submission function is look like this,
onSubmit(e) {
e.preventDefault();
const book_obj = {
book_no:this.unique_id_generator(),
isbn_no: this.state.isbn_no,
author: this.state.author,
published_year: this.state.published_year,
publisher: this.state.publisher,
category:this.state.category
};
axios.post('http://localhost:4000/book/', book_obj)
.then(res => console.log(res.data));
this.setState({
isbn_no: '',
author:'',
published_year: '',
publisher: '',
category:''
})
}
Lastly I would like to provide user routes code,
import {register,
authenticate,
authorization
} from '../controller/user_controller';
const user_routes = (app) => {
//SIGNUP
app.route('/auth/signup')
//POST A USER
.post(register);
//SIGNIN
app.route('/auth/signin')
//AUTHENTICATE USER
.post(authenticate);
}
export default user_routes;
Here are the areas where I am stuck:
How could I store these tokens in sessions to send with future requests?
How to attach and send token value with GET, POST, PUT, DELETE methods?
so to store the token in local storage:
const user: User = getUserFromBackend()
// assuming user is an object
localStorage.setItem('token', user.token);
// get token
const token = localStorage.getItem('token');
to make a request with token header:
const token = localStorage.getItem('token');
fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify(data), // body data type must match "Content-Type" header
})
.then(response => response.json())
Although late to answer. I'd suggest storing JWT in cookie if possible. Then you just read the cookie and pass JWT in headers for the request where you need authentication. I prefer to call the header key x-api-token and set its value to the JWT string.
Moreover, you just need to authenticate the user once from your backend by decrypting this JWT and verifying. Once JWT is verified, you can create a session for authenticated user. This session expires in half hour or whatever you've configured in your App.
This way you don't need to send JWT again as the session is valid. I'm not saying that sending JWT is not good and sometimes required in applications where you don't create a session for authenticated user. This happens when performing operations requiring one time authentications like payment processing.
Implementing this logic shouldn't be a problem using node's JWT-decode package (https://www.npmjs.com/package/jwt-decode).
Hope this helps.